UP | HOME

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).

Author: <schiptsov@gmail.com>

Email: lngnmn2@yahoo.com

Created: 2023-08-08 Tue 18:40

Emacs 29.1.50 (Org mode 9.7-pre)