Notes on Rust
Some notes for the rust programming language.
Borrow Checker
-
Ownership rules:
- Each value has one owner.
- There can only be one owner at a time.
- References will be dropped when go out of scope.
- The checker evaluates at compile time.
-
References and Borrowing:
- At any given time, you can have either
- 1 mutable reference, or
- any number of immutable references.
Lifetimes
The three rules
- Compiler assigns a lifetime parameter to each parameter that is a reference.
- If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
- If
&selfor&mut selfis one of the input lifetime parameters, the lifetime ofselfis assigned to all output lifetime parameters.
'static: Static Lifetime denotes that the reference can live for the entire duration of the program.
Tests
Should know:
#[cfg(test)]: module only compiled when running tests#[test]: function is a test functioncargo test: run testsjcargo test -- --test-threads=1to run tests in single threadcargo test <test_name>to run specific testcargo test -- --show-outputto show output of passing testscargo test -- --ignoredto run ignored tests
assert!,assert_eq!,assert_ne!: macros to check if conditions are met#[should_panic]: test should panic to pass, e.g.#[test]
#[should_panic] // Or #[should_panic(expected = "divide by zero")] to check panic message
fn test_divide_by_zero() {
let _ = 1 / 0;
}Result<T, E>: test can return a Result type instead of using assert macros, but can't use with#[should_panic].ignoreattribute to ignore specific tests, use after#[test]
Closure
Closure will automically implment 1, 2, or all of the following traits:
Fn: closure only borrows values from the environment immutablyFnMut: closure borrows values from the environment mutablyFnOnce: closure takes ownership of values from the environment (movekeyword)
Smart Pointers
Box<T>: for heap allocation, single ownership.Rc<T>: for shared ownership, reference counting, single-threaded.RefCell<T>: allows mutable borrows checked at runtime, even ifRefCell<T>is immutable, single-threaded.Rc<RefCell<T>>: shared ownership with interior mutability, single-threaded!
Weak<T>: non-owning reference toRc<T>, prevents reference cycles, created usingRc::downgrade(&rc).
Interior Mutability
Rust enforces borrowing rules at compile time. While it may reject safe code that violates these rules, interior mutability is a way to check these rules at runtime.
Concurrency
Arc<T>: Atomic Reference Counted, thread-safe version ofRc<T>.Mutex<T>: Mutual Exclusion, provides interior mutability with locking mechanism for thread safety.RwLock<T>: Read-Write Lock, allows multiple readers or one writer at a time, useful for read-heavy scenarios.
Threads can be spawned using std::thread::spawn and can be joined using the join method on the thread handle.
Send and Sync Traits
Send: Transfer ownership to another thread.Sync: Allow references to be shared between multiple threads.
Async tasks
The rust book introduced trpl library for async programming, below are some common uses:
-
trpl::spawn_task(future): spawn a new async task. -
trpl::join(futures, future2): wait for two futures to complete. -
trpl::join!(...futures): wait for multiple futures to complete. -
trpl::race(future1, futures2): wait for the first future to complete. -
trpl::channel: create a channel for communication between tasks (mpsc, async). -
Streams:
trpl::stream_from_iter(iter): create a stream from an iterator.trpl::ReceiverStream(rx): create a stream from a receiver.
Create futures
let my_future = async {
// async code here
};
let my_future_with_move = async move {
// async code here
};
my_future.await;
Pin for futures
Unlike other types, futures may be self-referential, meaning they can contain references to themselves. This can lead to issues when the future is moved in memory, as the references may become invalid.
To address this, Rust provides the Pin type, which prevents a value from being moved once it has been pinned. This is particularly useful for futures, as it ensures that the self-referential parts of the future remain valid for the duration of its execution.
Unpin and !Unpin:
By default, most types in Rust implement the Unpin trait, which means they can be safely moved in memory. However, some types, such as certain futures or self-referential structs, do not implement Unpin and are considered !Unpin. These types must be pinned to ensure their memory location remains stable.
When working with futures that are !Unpin, you typically use Pin<Box<dyn Future<Output = T>>> or Pin<&mut dyn Future<Output = T>> to pin them in place. This ensures that the future can be safely polled without the risk of invalidating any self-references.
