Rust¶
Rust provides memory and thread safety through ownership and static types without requiring a garbage collector. Its compiler makes invalid states and lifetimes visible early, but it cannot prevent deadlocks, leaks, incorrect business logic, or every performance problem. The maintained Rust Book is the primary learning reference.
Values and Control Flow¶
fn main() {
let name = "Ada"; // immutable binding
let mut attempts: u32 = 0; // mutable binding
attempts += 1;
let message = if attempts == 1 { "first" } else { "later" };
println!("{name}: {message}");
}
Blocks and if are expressions. Integer types have explicit size and signedness. Use checked, saturating, wrapping, or overflowing arithmetic when overflow behavior is part of the design.
Ownership¶
Each value has one owner; when the owner leaves scope, the value is dropped. Assignment or function calls may move ownership. Types implementing Copy, such as many small scalar values, are copied instead.
fn length(value: &str) -> usize {
value.len()
}
fn main() {
let text = String::from("hello");
println!("{}", length(&text));
println!("{text}"); // the function borrowed it
}
Borrowing creates references without taking ownership. At a given time, Rust permits either multiple immutable references or one mutable reference. References cannot outlive the referenced value.
Use owned String, Vec<T>, and other containers when a value must be stored or transferred. Accept borrowed &str, &[T], or &T when a function only needs to inspect existing data. Clone only when duplicate ownership is actually required.
Structs and Enums¶
#[derive(Debug, Clone, PartialEq, Eq)]
struct User {
id: u64,
name: String,
}
enum Command {
Create { name: String },
Delete(u64),
}
fn describe(command: &Command) -> String {
match command {
Command::Create { name } => format!("create {name}"),
Command::Delete(id) => format!("delete {id}"),
}
}
Enums carry variant-specific data and match is exhaustive. Use types to make invalid states unrepresentable where the added modeling remains understandable.
Errors¶
Option<T> represents possible absence; Result<T, E> represents recoverable success or failure. panic! is for violated invariants or unrecoverable program defects, not ordinary invalid input.
use std::{fs, io, path::Path};
fn read_count(path: &Path) -> Result<u64, io::Error> {
let text = fs::read_to_string(path)?;
text.trim()
.parse()
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
}
The ? operator returns errors while preserving the happy path. Libraries should expose meaningful error types; applications can add operational context at boundaries. Do not discard errors with unwrap() unless failure is impossible by a documented invariant or acceptable in a small test.
Traits and Generics¶
Traits define shared behavior:
trait Render {
fn render(&self) -> String;
}
fn print_rendered(value: &impl Render) {
println!("{}", value.render());
}
Generics normally use static dispatch; dyn Trait provides runtime dispatch through a trait object. Prefer concrete types until generic reuse or substitution is real. Public trait design has long-term compatibility costs.
Collections and Iterators¶
let values = vec![-2, 3, 4];
let total: i32 = values.iter().copied().filter(|v| *v > 0).map(|v| v * v).sum();
assert_eq!(total, 25);
iter() borrows items, iter_mut() mutably borrows them, and into_iter() consumes the collection. Iterator pipelines are lazy until consumed and commonly compile without intermediate allocations.
Strings are UTF-8 and cannot be indexed by an arbitrary integer. Iterate over bytes or Unicode scalar values according to the task; user-perceived grapheme clusters require a specialized library.
Shared Ownership and Interior Mutability¶
Box<T>owns heap-allocated data;Rc<T>provides single-threaded shared ownership;Arc<T>provides thread-safe shared ownership;Cell<T>andRefCell<T>move borrowing checks to runtime in single-threaded contexts;Mutex<T>andRwLock<T>synchronize shared state across threads.
These types solve different ownership needs; they do not justify making all state shared. Prefer one clear owner or message passing first.
Concurrency and Async¶
Rust threads use ownership plus Send and Sync traits to prevent many data races. Locks can still deadlock, and channels can still grow without bounds.
Async functions return futures that make progress when polled by a runtime. The standard language does not choose one runtime. Avoid holding synchronous locks across .await, bound spawned tasks, propagate cancellation, and use timeouts around remote work.
Unsafe Rust¶
Unsafe code permits operations whose proof obligations the compiler cannot verify; it does not disable Rust's rules. Keep unsafe blocks small, document each safety invariant, expose a safe interface, and test with appropriate analysis tools. The Rust Reference defines the language-level unsafe operations.
Cargo Projects¶
cargo new example
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo build --release
Commit Cargo.lock for applications and binaries; library policy depends on the intended consumption and current Cargo guidance. Review dependency features, build scripts, native code, licenses, and advisories. A crate can execute code during build.
Performance¶
Use debug builds for development and release builds for realistic performance. Measure allocations, copying, lock contention, I/O, algorithms, and cache behavior before changing types or adding unsafe code. Zero-cost abstraction means an abstraction can compile away when designed for it, not that every abstraction has no cost.
Checklist¶
- Is ownership transferred, borrowed, or genuinely shared?
- Are clones intentional and measured where important?
- Do enums and results represent absence and failure explicitly?
- Are locks, channels, tasks, and retries bounded?
- Is unsafe code isolated with documented proof obligations?
- Are dependency features and build scripts reviewed?
- Does the code pass formatting, lints, tests, and release-mode measurement?