top | item 34223557

Rust for Professionals

126 points| keewee7 | 3 years ago |overexact.com

100 comments

order

wrs|3 years ago

An important “unblocker” for me when learning Rust after decades of other languages was internalizing that assignment is destructive move by default. Like many Rust intros, this sort of glides past that in the “ownership” section, but I felt like it should be a big red headline.

If you’re coming from C++ especially, where the move/copy situation is ridiculously confusing (IMO), but also from a simpler “reference by default” language like Java, this has profound impact on what’s intuitive in the language.

For the C++ comparison, this is a pretty good article: https://radekvit.medium.com/move-semantics-in-c-and-rust-the...

scotty79|3 years ago

Also very important realization is that things that are moved around (assigned to variable, moved into or returned from a function, kept as a part of the tuple or a field of a struct) must have fixed and known size. And the variable itself is not a handle. It's a fixed sized area in the memory that you named and moved something into it.

This makes completely logical why some things must be Box<>'ed and borrowed. Why you cannot treat Trait or even impl Trait like any other type. Why sometimes it's ok to have impl Trait as your return type while in other cases it's impossible and you must Box<> it.

Third important realization is that things borrowed out of containers might be moved 'behind the scenes' by the container while you hold the borrow, so you are not allowed to mutate container while you are holding borrows to any of its contents. So it's ok, to instead hold indexes or copies or clones of keys if you need.

Another observation is that any struct that contains a borrow is a borrow itself. Despite using completely different syntax for how it's declared, created, it behaves exactly like a borrow and is just as restrictive.

Last thing are lifetimes, which don't have consistent (let alone intuitive) syntax of what should outlive what so are kinda hard to wrap your head around so you should probably start with deep understanding what should outlive what in your code and then look up how to express it in Rust syntax.

Rust is syntactically very similar to other languages, but semantically is fundamentally different beast. And while familar syntax is very good for adoption I'd also prefer tutorials that while showing the syntax explain why it means something completely different than what you think.

coderenegade|3 years ago

I found the copy/move situation in Rust to be far less intuitive than in C++. In C++, move semantics are obvious because they rely on std::move and the && operator, whereas in Rust, similar behavior seemed to depend on the object type. Even more confusingly, Rust has its own move operator as well, despite destructive move being the default behavior for assignment.

I found it frustrating enough that I put the language down and just went back to using C++.

attractivechaos|3 years ago

Nice blog post. Nonetheless, to a new learner like me, the hardest part of rust is not its syntax; it is the ownership management. Sometimes I easily know how to implement a task efficiently in other languages but I have to fight the compiler in rust. I either need to reorganize data structures, which takes effort, or to make a compromise by cloning objects, which affects performance. Note that I do agree with rust designers that it is better to make data dependencies explicit but learning to deal with them properly is nontrivial for me.

csomar|3 years ago

This is exactly why I prefer Rust, eh, for everything. It forces you to think harder about your data structures and to better organize/understand your program and how data flows, gets consumed and get output.

You can ignore that in other languages, but this comes at a cost later.

insanitybit|3 years ago

The short answer is... yeah, just clone the objects. Whatever other languages are doing is going to have the same tradeoff - performance (or safety, if the other languages aren't memory safe). Iff it becomes a problem, come back later and remove the '.clone()'.

aussiesnack|3 years ago

But it's so easy to get a handle on how the borrow checker works! Just check out this thread and all will become clear: https://users.rust-lang.org/t/what-is-a-good-mental-model-of...

Joke btw. That thread is a hilarious trainwreck - surely the final nail in the coffin for the Rust advocates who so often deny anything about Rust is difficult to learn.

I don't mean that as an anti-Rust jibe, in fact I'm planning to get back to it this year (having given up in despair last). I like much about it, and think it's tremendously practical for many purposes. But it just is a difficult language, no question.

xiphias2|3 years ago

In my opinion this is the way _not_ to learn Rust. These syntaxes are not important at all, and doesn't introduce lifetimes (which is by far the most important part of the language for deciding whether to use it or not).

Any blog about learning Rust for beginners should just contain information that helps the reader decide _whether_ she should put in the time required for learning it, then refer to the great Rust Programming Language book that's really hard to surpass.

The reference is great as well, though personally I miss a part that formally defines the type system in its current form (there are some papers about lifetimes, but they are very hard to read).

mlindner|3 years ago

It'd be nicer if there was some way of selection which language is shown on the left side. Expecting readers to understand both C++ and Kotlin and Java and Javascript will be a stretch for most.

kika|3 years ago

My experience with Java ended circa 2000 and I never wrote a single line in Kotlin. But I read these examples without noticeable issues.

heavyset_go|3 years ago

All of those languages adopt the C-like syntax and semantics, it shouldn't be hard for someone with familiarity with languages in that family to deduce what's being conveyed in code in languages they might not have experience with.

clumsysmurf|3 years ago

I thought there would be an option to select just one, but seems they are indeed just random smatterings of rust vs { Typescript, Javascript, Kotlin, Java, C, and C++ }

nine_k|3 years ago

AFAICT, the expectation is that the reader knows at least one modern programming language from the list, and maybe is acquainted in passing with a couple of others. So at least some comparisons should click.

(They seemingly don't use more apt comparisons with OCaml and Haskell, for instance, not expecting the reader to know them.)

86J8oyZv|3 years ago

These features aren’t each supported by all those languages though. I also don’t think expecting a dev interested in Rust to understand several C-like languages is unreasonable, at least enough ti understand these straightforward example cases.

deepsun|3 years ago

> Inner functions. Rust also supports inner functions. ...

> Closures (lambdas). Rust supports closures (also called Lambdas, arrow functions or anonymous functions in other languages).

That's misguiding.

Closures are not lambdas. Lambdas are just syntax, but the whole point about closures is that they capture the enclosing environment (have access to variables where it's defined). Rust's documentation states just that. Closures may or may not be lambdas.

In above example of "Inner functions" (which is also a closure) that would be more clearly explained if the inner function used an outside variable. Not all languages can do that.

joaquincabezas|3 years ago

I keep saving these Rust resources for a near future... Am i the only one??

I really hope to start using Rust in 2023, probably for some kind of API gateway experimentation

tmtvl|3 years ago

I saved up Common Lisp resources for a few years and in 2022 I finally decided to sit down and learn it. It was entirely worth it, so I recommend you sit down to learn Rust one weekend. In fact, do it next weekend. Getting started on anything is always better done sooner than later.

pjmlp|3 years ago

I can only justify Rust for hobby coding, none of the stuff I do professionaly cares about what Rust offers, compiled managed languages are good enough and have decades of software maturity, and using Rust as translation layer between them and C++ libraries hinders more than it helps.

rr808|3 years ago

Yeah I wish there was a strong jobs market for Rust developers then I'd feel like I wasn't wasting my time.

solomatov|3 years ago

This document only briefly mentions interior mutability, which IMO, is one of the most important things to become productive in Rust.

ridiculous_fish|3 years ago

Curious why the author chose not to discuss macros? You encounter them immediately with a Hello World.

John23832|3 years ago

Declarative macros would be too confusing to get into in any way other than "the ! in println! denotes a macro".

ww520|3 years ago

This is an excellent short tutorial. It helps to compare and contrast to other languages.

jason2323|3 years ago

As someone coming from a Java background, this seems useful! Thank you!

skor|3 years ago

any constructive criticism on rust syntax?

ridiculous_fish|3 years ago

With the caveat that syntax is the ultimate bikeshed topic, one (IMO) syntactic wart is the special pattern syntax:

1. "Operators" have different meanings. `1 | 2` and `1..=2` mean something different in patterns than in expressions. Here is a silly example: https://rust.godbolt.org/z/76Ynrs71G

2. Ambiguity around when bindings are introduced. Notice how changing a `const` to a `let` breaks the function: https://rust.godbolt.org/z/aKchMjTYW

3. Can't use constant expressions, only literals. Here's an example of something I expect should work, but does not: https://rust.godbolt.org/z/7GKE73djP

I wish the pattern syntax did not overlap with expression syntax.

scotty79|3 years ago

I don't like how

fn func<'a>() means that 'a must outlive execution time of func

but

T:'a means that references contained in T must live longer than 'a

and

'a:'b means 'a must live longer than 'b (that's consistent at least)

Maybe:

    fn 'a:func() {
or

    fn func() 'a:{
would be better for indicating that 'a should outlive function execution.

Maybe some directional character would be better than : (> is probably out of question because of <> generics)

----

I feel like structs that don't have 'static lifetimes because they contain some borrows should have it indicated in their name.

For example:

    struct Handle&<'a> { n:&'a Node }
or even

    struct Handle&'a { n:&'a Node }
or

    struct Handle& 'a:{ n:&'a Node }
to indicate that 'a must outlive the struct.

Then you could use it:

    let h = Handle& { n: &some_node };
Maybe functions that create non-static struct might have & at the ends in their names.

Like

    vec![].into_iter().map()
but

    vec![].iter&().map()
    
You could easily see that you are dealing with something you should treat like a borrow because it contains borrows. Such structs would be sort of named, complex borrow and raw '&' borrow would be anonymous or naked borrow.

Not sure if it would also be nice to differentiate structs with &mut

----

I would just like to separate lifetimes syntax from generics syntax because those two things have nothing to do with each other from the point of view of the user of the language.

----

I would also like to have

while cond {} else {}

where else is only entered if cond was false from the start. But that's a wish not specific to Rust.

tmtvl|3 years ago

Using clearly defined bit sizes (i32, f64) rather than legacy naming conventions (int, double) is a good idea, the language could be really quite something if they switch to S-expressions.

legerdemain|3 years ago

Syntax or semantics? Not a lot for syntax... maybe the "turbofish" syntax with generic types is a bit too much line noise: <Foo<_> as Bar<_>>::baz<_>()

tomr75|3 years ago

the hardest part and barrier are the concepts behind lifetimes/ownership/borrowing not the syntax

scotty79|3 years ago

I think the hard part is understanding how limited is basic feature set of just Rust.

That you can write very few interesting programs without venturing into the heap with Box, Rc and such and into internal mutability with Cell and RefCell.

Then it quickly raises to the power of other languages and surpasses them with "pay for only what you use" mentality.