Re-reading Gentle introduction to Rust, chapter 2, "Structs, Enums and Matching".

  • Important concept: moving values. Rust assignment does not, by default, copy a value, or add a reference to the value, but moves it. This means that if a variable is assigned to another, the original variable can no no longer be used. This can be controlled by doing explicit copies (clone method), or implicit ones using the copy trait.

  • This only affects "non-primitive types", basically anything that is not a machine word or a small on-stack struct. It affects especially (only?) things that are heap allocated.

  • It's all about managing memory management and borrowing.

  • I like that Rust makes this explicit and non-magic.

  • The rustdoc generated stuff have too many invisible links (titles, etc), making it difficult to click on an area to get focus there. This causes accidental navigation, and it's unnecessarily difficult to get back, for some reason. (Also, WTF do I need to move keyboard focus that way? Stupid web stuff.)

  • Important concept: variable scoping. Block scoping. Loop scope. Scope ties into memory management: when execution leaves a scope, all variables in that scope are "dropped", which can trigger heap memory to be reclaimed. This is lovely.

  • New thing: tuples. Not very exciting, but indexing syntax is a little unusual: t.42. Not sure if index can be any integral expression or if it has to be constant.

  • I dislike the example that uses first and last name fields, even as an example. It perpetrates the falsehood that everyone has first and last names.

  • New thing: structs. Not exciting, as such, but very important. Notably, these aren't classes, and Rust isn't an object oriented language. I think I'm going to like that, even if it means rearraning my brain a bit.

  • New thing: Associated functions and impl blocks. Very interesting. This feels like it'll be crucial for making clean code. Having to use them even for such common things as constructors could be a little weird, but since a constructor is going rarely going to be the only associated function, using the same approach for everything makes a lot of sense. I like that there is no magic name for the constructor, that new is merely a convention.

  • The magic &self argument to associated functions is a litle magic, but it saves having to write out the full type, so it's OK.

  • New thing: #[derive(Debug)] to automatically add the Debug trait. I expect this will become part of the boilerplate for most structs, but it's useful to have it not be mandatory, to save on code size.

  • Important thing: lifetimes. For Rust to manage heap values correctly, it needs to know how long each value needs to live. This is handled by allowing the programmer to specify the lifetime. Enables better correctness analysis by compiler, leading to fewer programming errors.

  • Important thing: traits. These provide the kind of functionality in Rust that inheritance provides in OO languages. A bit like interfaces. A trait defines an interface for a type, meaning functions that can operate on values of that type. The functions can then be implemented for different types, and Rust keeps track of which implementation is called, by virtue of static typing.

  • Also, interestingly, one can add new methods to existing types by defining new traits and implementing them. Including built-in types like integers.

  • I should eventually study the Rust std basic traits.

  • Traits are correctly used by Rust a lot. For example, to implement an iterator, you implement the std::iter::Iterator trait.

  • New thing: associated type for traits. Type parameters.

  • New thing: trait bounds. Essentially requirements on type parameters. More things to tell the compiler, so it can save me from making mistakes. Like. At the same time I foresee that this will require me to learn a lot of details.

  • New thing: enums. Much nicer than C enums.

  • I'm not sure I understand the last two examples in Enums in their full glory. Why is it OK to return the extracted string value in an Option?

  • Closures and borrowing seems complicated. I may want to stick to very simple closures for now.

  • Interesting point: Rust speed requires programmer to type more, to be more explicit about types and lifetimes and so on. Javascript, Python, etc, are terser languages, but suffer runtime speed penalties for that. I am OK with Rust's tradeoff.

  • New thing: Box.

  • New thing: type to create type aliases, like typedef in C.

Edited to add, based on feedback from my friend:

  • t.N syntax for indexing tuples only works for constants. t[i] works for any expression.

  • It seems I misunderstood associated functions. It seems an associated function is just a function in the impl block, but not a method. A method needs to also get the value (or a reference to it) as its first argument: a self, or &self, or &mut self argument. A method is an associated function, but an associated function need not be method.

  • Traits can provide default implementations for functions. This is super-powerful.

  • Re the enum full glory example: Given a variable x, when a function is called as foo(x) the value of x is moved into the function, and cannot be used on the caller. If the call is foo(&x), then the value is borrowed and so the caller can still use x. In the book's match example, what doesn't work is matching against the value, since that moves the value out of the Value, and that fails, because the Value is itself borrowed from the caller: moving anything out from a borrowed value breaks Rust's rules for keeping track of what's owned by whom. Also: you're not allowed to give away something you've borrowed, in real life, either. Having the match return a reference instead means borrowing further, and that's OK.