Overview of Rust

Started by Mozilla employee Graydon Hoare in 2006. Mozilla started sponsoring the project in 2009. From 2013 to 2015 the garbage collector was phased out in favor of the ownership system. In 2021, the Rust Foundation was established, with founding members AWS, Huawei, Google, Microsoft, and Mozilla. Features that make Rust stand out include:

  1. algebraic/sum types in enums with associated data - notably Option which procludes a need for "null";
  2. convenient pattern matching syntax;
  3. other zero-cost abstractions like iter, traits;
  4. "fearless concurrency" and memory control via the ownership system and borrow checker

Literals/slice and Heap Strings

Both are UTF-8 encoded. Because it is variable-width (chars are 1 to 4 bytes long), Rust disallows direct indexing.

&str
Immutable string slice, a view into a string. Either a string literal like "hello", i.e. a ref. to bytes stored in the binary; or a String slice, created with &s. This reference type doesn't own memory / require allocation, and follows borrow checker rules.
String
Heap-allocated, mutable/growable string, owning its data. Moves by default. Creates with String::from("text"), or from slice like "text".to_string(). Appends using either push() or push_str(). Dynamic construction via format!.

Collection types from std::collections

Due to its philosophy of high efficiency/systems programming approach and explicitness the language provides many data structures for collections.

Vec<T>
Contiguous memory sized capacity*|T|. [i]/get/pop is O(1), push is amortized O(1), insert is O(N). Capacity starts at 0 and doubles each time it needs to grow.
LinkedList<T>
.iter().nth() is O(N), push_front/push_back is O(1). It may be iterated via cursor_front/cursor_back. Cannot insert at a specific index without iterating.
HashSet<T> and HashMap<K, V>
Unordered collection of key-value pairs. HashSet is a special case of HashMap where keys represent the values, and every value is ().
BTreeSet<T> and BTreeMap<K, V>
Ordered collection of key-value pairs implemented as a B-Tree. The Key type needs traits Ord and Eq. Operations are O(logN).
VecDeque<T>
Double-ended queue implemented with growable ring buffer. push_front/push_back is amortized O(1).
BinaryHeap<T>
A max-heap tree, i.e. the largest element always at the top/root. This is used for priority queues: push is O(logN), pop is O(logN), peek the lookup of the largest element is O(1). The type needs trait Ord.

Compound types

Compound types group multiple values into one type. There are two primitive compound types, tuples and arrays.

Tuples
Fixed length, may mix types. This is typed with the matching type tuple e.g. for a tuple of a 32-bit signed integer and a 64-bit floating point, let a: (i32, f64) = (123, 4.56) Access via dot-index notation, e.g. a.1 // == 4.56
Arrays
Fixed length, uniform type. This is typed [type; length] e.g. for an array of four 32-bit signed integers, let a: [i32; 4] = [1, 2, 3, 4]; Access via bracket-index notation, e.g. a[1] // == 2

The ownership system

Rust has neither garbage collector nor alloc/freeing of memory. It uses the ownership system, checked at compile-time, memory safe but not slowing the program at run time.

Ownership is singular
Each value has exactly one owner. When assigning a new value to an existing variable, the previous value is dropped (i.e. the memory is freed)
Ownership is scoped
Values are dropped when owner goes out of scope.
Ownership is transferable
Assigning an existing value to a new variable immediately invalidates the prior. This is called a move, preventing double-free issues, v.s. shallow copy in languages where both would remain valid.

The borrow checker

Since passing a value directly to function like func(someStr) would invalidate the variable in the scope, Rust implements borrowing like func(&someStr). This is immutable, vs. func(&mut someStr). The borrow checker is a crucial part of the Rust compiler rustc. It enforces the borrowing rules via static analysis.
The borrowing rules are:

  1. Any borrow must last for a scope no greater than that of the owner, which prevents dangling pointers.
  2. You can have many immutable XOR a single mutable reference, which prevents data races.
    let r1 = &mut s; let r2 = &mut s;
    let r1 = &mut s; let r2 = s;
    let r1 = s; let r2 = s;

The Drop trait

Implementing the Drop trait can be used to manually free-up memory and other relevant resources. The struct fields are dropped recursively. .drop() cannot be called directly, but to force ealier dropping, std::mem::drop() can be used.

    struct MyStruct {}
    impl Drop for MyStruct {
        fn drop(&mut self) { /* ... */ }
    }
    fn main() {
        let foo = MyStruct::new();
    } // at this point, drop() is called.