A better Rust
Google released a nicely worded (clarity and briefness) course about the basics of Rust
https://google.github.io/comprehensive-rust/
There, I think, is something to add:
Rust is slowly incorporating the proper, emergent modern features from the better classic languages.
At a higher level Rust adds stricter type-discipline for references, and lifts lifetimes into the type system.
Aside from &str
, the grow-able “Buffer” types are just proper ADTs, which is exactly the right thing.
Type inference
There are some new principles, which emerged in classic languages with a type-inference.
Types are inferred from literals and from the context (of operators being used).
This means one should take a greater care of writing literals and omit the type annotation clutter.
Immutable References
These are explicit variants of immutable bindings in classic functional languages. Old binding become “invisible” (but still valid) with shadowing.
Automatic dereferencing makes the code less cluttered. Use it consistently everywhere.
Bindings
Unlike the classic functional languages, Rust has assignment, which conceptually is similar to moving a value to a new location.
This is exactly what the mov
CPU instructions do semantically.
In Rust, however, the “data” (in memory) is reused as with aliasing, but the “source” becomes unaccessible (gets invalidated).
In Rust there is always exactly one “localtion” (and a corresponding variable) for every value – no aliasing bugs.
Conceptually, a variable (location in memory) “owns” a value, and the compiler enforces the ownership rules.
Ownership
Every value is “owned” by exactly one vaeiable and is moved from one to another with an assignemrny.
The type-system knows (and checks) “who owns what” (every value), abd for how long (lifetimes).
The concept of an ownership is about when and where (by whom) deallocation will be performed.
The notion of a borrowing (and moving) are about the retaining (or changing) of the “ownership”.
The main principle is that a values becomes unaccessible once it “mut-borrowed” or “moved” (a compile-time error).
This is so-called “resource safety” - cannot access what one does not own anymore (gave it up).
Borrowing
This is a meme-word for using references (both immutable and mutable).
The ownership is being retained by the “current owner” or “caller” of a function which takes references.
The point is that the actual values will be “dropped” at the end of the “owner” scope, and the references will become invalidated then and there.
For function calls, however, references will never outlive the returns.
Explicit taking of references with the &
operator will be tracked at the type-system level.
The compiler imposes additional discipline by tracking all the referenes and their lifetimes.
Mutable references
At every moment in time (it is still an imperative language) there guaranteed (by static typing) to me at most one mutable reference to the data.
OR any number of immutable references (which must never outlive the “owner” of a referenced value).
When a mutable reference is being taken, all other references becomes invalidated by the compiler.
Lifetimes
Rust tracks ligetimes of all values and references. This prevents “use after free” and “dangling references” at the type-system level.
The compiler checks the scoping rules (for the current “owner”), so there will be no “dangling references”.
It insures that there is at most one mutable reference at a time.
It allows to annotate lifetimes of a value and its references (to be the same).
The Move Semantics by default
Again, just like mov
CPU instruction, values conceptually “move” with an assignment (as if to a new memory location - into a new variable).
This is a necessary imperative notion. In fact only the metadata (pointer, len, capacity) gets updated, which is borrowed from the FP world.
The “source” (of an assignemnt operator) becomes “empty”, and the “target” variable becomes the “binding”, and the data is reused, not copied.
So, the assignment operator moves (conceptually – rebinds) the value, not copies it, and the source becomes invalidated (the transfer of ownership thus occurred).
Copy and Clone traits has to be explicitly implemented (good!). Scalars are just copied.
#[derive(Copy, Clone, Debug)]
is really cool.
Methods
This is the modularity aspect, when a value “carries all its methods with it”. According to Joe Armstrong, this is just a fundamentally wrong notion, but it seems OK as long as it just a syntactic sugar for calling a function with an implicit zrtoth argument (the self).
Semantically, this is equivalent to a proper ADT with an implicit first parameter (self).
This is what the early C++
did with structs back then. This was a real syntactic innovation.
Nowadays Rust generalizes this to most user-defined “compound” types via an impl
block, which is an implicit nested sub-mofule.
Traits
Similar to type-classes – a composition (a product of methods) instead of inheritance (a set-subset operation).
Again, this is the right thing to do (not everything in the Universe breaks into peoper non-overlapping nested categories).