Rust Handbook
Rust is a systems programming language designed by Mozilla and first released in 2015. Its ownership and borrow-checker model guarantees memory and thread safety at compile time — without a garbage collector. Zero-cost abstractions, LLVM-backed codegen, and no runtime overhead give it C/C++-level throughput. Rust has been voted "most loved language" in Stack Overflow surveys every year since 2016 and is now used in the Linux kernel, Windows kernel, Android, Cloudflare Workers, Amazon S3, and the Rust-rewritten parts of Firefox.
Pick Rust when
- Memory safety without a GC — the borrow checker eliminates use-after-free, double-free, null pointer dereference, and data races at compile time. This is Rust's defining property.
- Systems programming with safety — writing an OS component, device driver, embedded firmware, or database storage engine where C would be the alternative. Rust is now the only language besides C/C++ allowed in the Linux kernel.
- WebAssembly — Rust compiles to WASM with the smallest possible runtime footprint. It is the dominant language for WASM-based edge compute (Cloudflare Workers, Fastly Compute).
- CLI tools — Rust produces statically linked binaries with no runtime dependency. Tools like ripgrep, fd, bat, and exa replaced their Unix equivalents with Rust implementations that are measurably faster.
- High-performance network services — async Rust (Tokio, async-std) handles millions of concurrent connections with predictable latency. No GC pauses, no JVM warm-up.
- Replacing C/C++ in an existing system — Rust has an excellent FFI for calling C and being called by C. You can rewrite one module at a time.
Think twice before choosing Rust when
- You need to move fast — the borrow checker has a steep learning curve. Fighting the compiler for the first few weeks is normal. Productivity is lower than Go, Python, or TypeScript until the team is proficient.
- Prototyping or exploratory code — Rust's strictness makes throw-away code expensive to write. Use Python or TypeScript to validate ideas, then rewrite the hot path in Rust if needed.
- You need a large existing ecosystem — the crate ecosystem (crates.io) is growing fast but is younger than npm or PyPI. Niche domain libraries may not exist yet.
- Compile times are critical — Rust compilation is slow for large projects. Incremental builds help, but cold builds of a large Rust project can take minutes.
Rust vs. its closest alternatives
- Rust vs C — same performance, but Rust prevents entire classes of C bugs at compile time. For new code, Rust is almost always safer. For targeting every obscure architecture or integrating into a C-only toolchain, C wins.
- Rust vs C++ — Rust's safety guarantees are stricter. C++ is more expressive and has a larger ecosystem, but memory bugs are common. New systems projects with no C++ legacy should strongly consider Rust.
- Rust vs Go — Go is much easier to learn and has a built-in scheduler. Rust is faster (no GC, predictable latency), and safer. Go for developer productivity and ops simplicity; Rust for systems programming and maximum performance.
Resources
- rust-lang.org — official Rust website
- The Rust Book — the definitive free introduction
- Rust by Example — learn by runnable examples
- Standard Library docs — complete std API reference
- crates.io — the Rust package registry
- The Rustonomicon — advanced guide to unsafe Rust
Topics
Variables & Types
rust
// Immutable by default — must opt-in to mutability
let x = 5; // i32 inferred
let mut y = 10; // mutable
y += 1;
// Explicit types
let a: i32 = -100;
let b: u64 = 1_000_000; // underscores for readability
let c: f64 = 3.14;
let d: bool = true;
let e: char = 'Z'; // Unicode scalar value
let s: &str = "hello"; // string slice (borrowed)
let owned: String = String::from("world");
// Constants — must be type-annotated, computed at compile time
const MAX_POINTS: u32 = 100_000;
static APP_NAME: &str = "myapp"; // static lifetime
// Shadowing — rebind with same name (may change type)
let z = 5;
let z = z + 1; // new binding, shadows previous
let z = z.to_string(); // now &str -> shadow changes type
// Numeric types cheat-sheet
// Signed: i8 i16 i32 i64 i128 isize
// Unsigned: u8 u16 u32 u64 u128 usize
// Float: f32 f64
// usize / isize = pointer-width (used for indexing)
// Casting
let n: i32 = 256;
let m = n as u8; // truncates to 0 (wrapping cast)
// Tuples
let tup: (i32, f64, bool) = (500, 6.4, true);
let (tx, ty, tz) = tup; // destructure
let first = tup.0; // index access
// Arrays — fixed length, stack-allocated
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let len = arr.len(); // 5Ownership
rust
// RULE 1: Each value has exactly one owner.
// RULE 2: When the owner goes out of scope, the value is dropped.
// RULE 3: There can only be one owner at a time.
fn main() {
// Move semantics — heap-allocated String
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED into s2; s1 is no longer valid
// println!("{}", s1); // compile error: value borrowed after move
// Clone — explicit deep copy
let s3 = String::from("hello");
let s4 = s3.clone(); // both s3 and s4 are valid
println!("s3={} s4={}", s3, s4);
// Copy types — stack-only types implement Copy (i32, bool, f64, char, tuples of Copy)
let n1: i32 = 5;
let n2 = n1; // n1 is COPIED, not moved
println!("n1={} n2={}", n1, n2); // both valid
// Ownership through functions
let s = String::from("world");
takes_ownership(s); // s is moved into function
// s is invalid here
let x = 5;
makes_copy(x); // x is copied; still valid
println!("x={}", x);
// Return ownership
let s5 = gives_ownership(); // function returns ownership to s5
let s6 = String::from("hello");
let s7 = takes_and_gives_back(s6); // s6 moved in, s7 gets ownership back
}
fn takes_ownership(s: String) { println!("{}", s); } // s dropped here
fn makes_copy(n: i32) { println!("{}", n); }
fn gives_ownership() -> String { String::from("new") }
fn takes_and_gives_back(s: String) -> String { s }Borrowing & References
rust
// Borrowing = taking a reference without taking ownership
fn main() {
let s = String::from("hello");
// Shared (immutable) reference — & — many allowed simultaneously
let r1 = &s;
let r2 = &s;
println!("r1={} r2={}", r1, r2); // both fine
// Mutable reference — &mut — ONLY ONE at a time
let mut s2 = String::from("hello");
{
let r3 = &mut s2;
r3.push_str(", world");
} // r3 goes out of scope here
println!("{}", s2); // now we can use s2 again
// Cannot mix shared + mutable borrow of same value simultaneously
// let r4 = &s2;
// let r5 = &mut s2; // compile error
// References must not outlive the data (dangling reference prevention)
let reference = dangle_safe();
println!("{}", reference);
// Slices — references to a contiguous sequence
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3]; // [2, 3]
let s3 = String::from("hello world");
let word: &str = &s3[0..5]; // "hello"
println!("slice={:?} word={}", slice, word);
}
// Return owned String instead of reference to local
fn dangle_safe() -> String {
String::from("owned")
}
// Lifetime intro: compiler ensures this reference is valid
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' { return &s[0..i]; }
}
&s[..]
}Control Flow
rust
fn main() {
// if / else if / else — expressions, no parens needed
let n = 7;
let msg = if n < 0 { "negative" } else if n == 0 { "zero" } else { "positive" };
println!("{}", msg);
// match — exhaustive, expression
let x: i32 = 2;
match x {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=10 => println!("four to ten"),
_ => println!("other"), // catch-all
}
// match returns a value
let description = match x {
1 => "one",
_ => "many",
};
// loop — infinite, can return a value with break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 { break counter * 2; }
};
println!("result={}", result); // 20
// while
let mut n = 3;
while n != 0 { n -= 1; }
// for over a range
for i in 0..5 { print!("{} ", i); } // 0 1 2 3 4
for i in 0..=5 { print!("{} ", i); } // 0 1 2 3 4 5
// for over a collection
let v = vec![10, 20, 30];
for val in &v { println!("{}", val); }
for (i, val) in v.iter().enumerate() { println!("{}:{}", i, val); }
// loop labels for nested breaks
'outer: for i in 0..5 {
for j in 0..5 {
if i == 2 && j == 2 { break 'outer; }
}
}
// while let
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() { println!("{}", top); }
}Functions
rust
// Functions declared with fn, snake_case by convention
fn add(a: i32, b: i32) -> i32 {
a + b // last expression is returned (no semicolon)
}
// Explicit return
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { return None; }
Some(a / b)
}
// Multiple return via tuple
fn min_max(v: &[i32]) -> (i32, i32) {
let min = *v.iter().min().unwrap();
let max = *v.iter().max().unwrap();
(min, max)
}
// Unit return — () — implicit when no -> type
fn greet(name: &str) { println!("Hello, {}!", name); }
// Closures — anonymous functions capturing environment
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(f(x)) }
fn main() {
println!("{}", add(3, 4)); // 7
println!("{:?}", min_max(&[3, 1, 4, 1, 5, 9])); // (1, 9)
// Closure with type inference
let square = |x| x * x;
let double = |x: i32| -> i32 { x * 2 };
println!("{}", apply_twice(double, 3)); // 12
// Move closure — takes ownership of captured variables
let s = String::from("hello");
let print_s = move || println!("{}", s);
print_s();
// s is no longer available here
// Higher-order: functions as parameters and return values
let ops: Vec<Box<dyn Fn(i32) -> i32>> = vec![
Box::new(|x| x + 1),
Box::new(|x| x * 2),
];
let result: i32 = ops.iter().fold(5, |acc, f| f(acc));
println!("{}", result); // (5+1)*2 = 12
}Structs & impl
rust
use std::fmt;
// Struct definition
struct Point { x: f64, y: f64 }
// Tuple struct
struct Color(u8, u8, u8);
// Unit struct (for trait implementations)
struct Unit;
// Struct with lifetime
struct Important<"a> { part: &"a str }
// impl block — associated functions and methods
impl Point {
// Associated function (no self) — called as Point::new(...)
fn new(x: f64, y: f64) -> Self { Point { x, y } }
fn origin() -> Self { Point { x: 0.0, y: 0.0 } }
// Methods take &self (read), &mut self (mutate), or self (consume)
fn distance(&self, other: &Point) -> f64 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
fn translate(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
}
// Implement Display trait
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Struct update syntax
#[derive(Debug, Clone)]
struct Config {
debug: bool,
verbose: bool,
timeout: u32,
}
fn main() {
let p1 = Point::new(0.0, 0.0);
let p2 = Point { x: 3.0, y: 4.0 };
println!("distance: {}", p1.distance(&p2)); // 5.0
println!("p2: {}", p2); // (3, 4)
let Color(r, g, b) = Color(255, 128, 0);
println!("rgb({}, {}, {})", r, g, b);
let base = Config { debug: false, verbose: false, timeout: 30 };
let debug_cfg = Config { debug: true, ..base }; // struct update
println!("{:?}", debug_cfg);
}Enums, Option & Result
rust
// Enum variants can carry data — like algebraic data types
#[derive(Debug)]
enum Shape {
Circle(f64), // tuple variant
Rectangle { w: f64, h: f64 }, // struct variant
Triangle(f64, f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle { w, h } => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
}
// Option<T> — the absence of null
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
// Result<T, E> — recoverable errors
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.trim().parse::<i32>()
}
fn main() {
let c = Shape::Circle(5.0);
println!("area: {:.2}", c.area());
// Option usage
match divide(10.0, 2.0) {
Some(v) => println!("result: {}", v),
None => println!("division by zero"),
}
// Option combinator methods
let opt: Option<i32> = Some(42);
let doubled = opt.map(|x| x * 2); // Some(84)
let filtered = opt.filter(|&x| x > 50); // None
let or_else = filtered.unwrap_or(0); // 0
let unwrap_or_default: i32 = None.unwrap_or_default(); // 0
// if let — concise single-variant match
if let Some(v) = divide(9.0, 3.0) {
println!("{}", v); // 3
}
// Result usage
match parse_int("42") {
Ok(n) => println!("parsed: {}", n),
Err(e) => println!("error: {}", e),
}
let n: i32 = parse_int(" 7 ").unwrap_or(0);
let n2 = parse_int("bad").unwrap_or_else(|e| { eprintln!("warn: {}", e); -1 });
}Traits
rust
use std::fmt;
// Trait definition — interface contract
trait Summary {
fn summarize(&self) -> String;
// Default implementation (can be overridden)
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..20.min(self.summarize().len())])
}
}
struct Article { title: String, body: String }
struct Tweet { username: String, content: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, self.body)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// Trait bound in function signature
fn notify(item: &impl Summary) { println!("Breaking: {}", item.summarize()); }
// Equivalent with where clause (cleaner for complex bounds)
fn notify_generic<T>(item: &T) where T: Summary + fmt::Debug {
println!("{:?} says: {}", item, item.summarize());
}
// Returning impl Trait (concrete type hidden from caller)
fn make_summarizable() -> impl Summary {
Tweet { username: String::from("bot"), content: String::from("hello") }
}
// Common derivable traits
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Score(u32);
// Operator overloading via std::ops
use std::ops::Add;
#[derive(Debug, Clone, Copy)]
struct Vec2 { x: f64, y: f64 }
impl Add for Vec2 {
type Output = Vec2;
fn add(self, rhs: Vec2) -> Vec2 { Vec2 { x: self.x + rhs.x, y: self.y + rhs.y } }
}
// Display
impl fmt::Display for Vec2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let a = Article { title: String::from("Rust"), body: String::from("is awesome") };
notify(&a);
let v1 = Vec2 { x: 1.0, y: 2.0 };
let v2 = Vec2 { x: 3.0, y: 4.0 };
println!("{}", v1 + v2); // (4, 6)
}Generics
rust
// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest { largest = item; }
}
largest
}
// Generic struct
#[derive(Debug)]
struct Pair<T> { first: T, second: T }
impl<T> Pair<T> {
fn new(first: T, second: T) -> Self { Pair { first, second } }
}
// Conditional method impl — only when T: Display + PartialOrd
use std::fmt;
impl<T: fmt::Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.first >= self.second {
println!("Largest is first: {}", self.first);
} else {
println!("Largest is second: {}", self.second);
}
}
}
// Multiple generic parameters
fn zip_map<A, B, C, F>(a: &[A], b: &[B], f: F) -> Vec<C>
where F: Fn(&A, &B) -> C {
a.iter().zip(b.iter()).map(|(x, y)| f(x, y)).collect()
}
// Generic enum (like Option/Result in std)
#[derive(Debug)]
enum Either<L, R> { Left(L), Right(R) }
// Turbofish syntax for type parameter specification
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("largest: {}", largest(&numbers)); // 100
let p = Pair::new(5, 10);
p.cmp_display();
let sums = zip_map(&[1, 2, 3], &[10, 20, 30], |a, b| a + b);
println!("{:?}", sums); // [11, 22, 33]
// Turbofish
let parsed = "42".parse::<i32>().unwrap();
let collected = (0..5).collect::<Vec<_>>();
println!("parsed={} collected={:?}", parsed, collected);
}Collections
rust
use std::collections::{HashMap, HashSet};
fn main() {
// Vec<T> — growable array
let mut v: Vec<i32> = Vec::new();
v.push(1); v.push(2); v.push(3);
let v2 = vec![4, 5, 6]; // macro shorthand
println!("len={} cap={}", v.len(), v.capacity());
println!("first={:?}", v.get(0)); // Some(1) — safe access
println!("raw={}", v[0]); // 1 — panics if OOB
v.extend(&v2);
v.retain(|&x| x % 2 == 0); // keep evens
let doubled: Vec<i32> = v.iter().map(|&x| x * 2).collect();
println!("{:?}", doubled);
// String — owned, heap-allocated, UTF-8
let mut s = String::new();
s.push_str("hello");
s.push(' ');
s += "world"; // AddAssign calls push_str
let joined = format!("{} {}", "foo", "bar");
println!("bytes={} chars={}", s.len(), s.chars().count());
// String slicing (byte indices — must be char boundaries)
let hello = &s[0..5];
// Iterate chars safely
for c in s.chars() { print!("{}", c); }
// HashMap<K, V>
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.insert(String::from("Bob"), 20);
// entry API — insert if absent
scores.entry(String::from("Alice")).or_insert(50); // no-op, already exists
scores.entry(String::from("Carol")).or_insert(30); // inserts Carol=30
// Increment with entry
let text = "hello world hello";
let mut word_count: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
*word_count.entry(word).or_insert(0) += 1;
}
println!("{:?}", word_count);
// HashSet<T>
let mut set: HashSet<i32> = HashSet::new();
set.insert(1); set.insert(2); set.insert(3);
let set2: HashSet<i32> = [2, 3, 4].iter().cloned().collect();
let union: HashSet<_> = set.union(&set2).collect();
let intersect: HashSet<_> = set.intersection(&set2).collect();
let difference: HashSet<_> = set.difference(&set2).collect();
println!("union={:?} intersect={:?} diff={:?}", union, intersect, difference);
}Error Handling
rust
use std::fmt;
use std::num::ParseIntError;
// Custom error type
#[derive(Debug)]
enum AppError {
ParseError(ParseIntError),
NegativeNumber(i32),
TooBig(i32),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::ParseError(e) => write!(f, "parse error: {}", e),
AppError::NegativeNumber(n) => write!(f, "negative number: {}", n),
AppError::TooBig(n) => write!(f, "number too big: {}", n),
}
}
}
// Implement std::error::Error for interoperability
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self { AppError::ParseError(e) => Some(e), _ => None }
}
}
// From conversions enable ? operator
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self { AppError::ParseError(e) }
}
fn parse_bounded(s: &str) -> Result<i32, AppError> {
let n: i32 = s.trim().parse()?; // ? converts ParseIntError via From
if n < 0 { return Err(AppError::NegativeNumber(n)); }
if n > 100 { return Err(AppError::TooBig(n)); }
Ok(n)
}
// ? in main requires Box<dyn Error>
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Result combinators
let doubled = parse_bounded("21").map(|n| n * 2)?;
println!("doubled: {}", doubled); // 42
let result = parse_bounded("bad")
.map_err(|e| format!("failed: {}", e))
.unwrap_or_else(|msg| { eprintln!("{}", msg); 0 });
// and_then chains fallible operations
let chained = parse_bounded("50")
.and_then(|n| if n == 50 { Ok(n * 3) } else { Err(AppError::TooBig(n)) });
println!("{:?}", chained); // Ok(150)
// panic! — for unrecoverable bugs (not expected errors)
// let v: Vec<i32> = vec![];
// let _ = v[0]; // panics with index out of bounds
// unwrap/expect — use only in tests or when truly impossible to fail
let n: i32 = "42".parse().expect("hardcoded literal; always valid");
println!("n={}", n);
Ok(())
}Iterators
rust
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Lazy iterator chain — nothing executes until consumed
let result: Vec<i32> = v.iter()
.filter(|&&x| x % 2 == 0) // keep evens: [2,4,6,8,10]
.map(|&x| x * x) // square: [4,16,36,64,100]
.take(3) // first 3: [4,16,36]
.collect();
println!("{:?}", result);
// fold — general reduction
let sum: i32 = v.iter().fold(0, |acc, &x| acc + x);
println!("sum={}", sum); // 55
// Common consumers
let any_even = v.iter().any(|&x| x % 2 == 0); // true
let all_pos = v.iter().all(|&x| x > 0); // true
let max = v.iter().max(); // Some(10)
let count5 = v.iter().filter(|&&x| x > 5).count(); // 5
let position = v.iter().position(|&x| x == 7); // Some(6)
// flat_map — map then flatten
let words = vec!["hello world", "foo bar"];
let chars: Vec<&str> = words.iter()
.flat_map(|s| s.split_whitespace())
.collect();
println!("{:?}", chars); // ["hello", "world", "foo", "bar"]
// zip + unzip
let keys = vec!['a', 'b', 'c'];
let vals = vec![1, 2, 3];
let zipped: Vec<_> = keys.iter().zip(vals.iter()).collect();
println!("{:?}", zipped);
// chain
let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let chained: Vec<_> = a.iter().chain(b.iter()).collect();
// Custom Iterator
struct Counter { count: u32, max: u32 }
impl Counter {
fn new(max: u32) -> Counter { Counter { count: 0, max } }
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < self.max { self.count += 1; Some(self.count) }
else { None }
}
}
let pairs: Vec<_> = Counter::new(5).zip(Counter::new(5).skip(1)).collect();
println!("{:?}", pairs); // [(1,2),(2,3),(3,4),(4,5)]
}Closures
rust
fn main() {
// Closures capture their environment
let x = 10;
let add_x = |n| n + x; // borrows x (Fn)
println!("{}", add_x(5)); // 15; x still usable
let mut count = 0;
let mut increment = || { count += 1; count }; // borrows mutably (FnMut)
println!("{}", increment()); // 1
println!("{}", increment()); // 2
// println!("{}", count); // error: borrowed mutably above
// move — takes ownership of captured vars (needed for threads)
let s = String::from("hello");
let owns_s = move || println!("{}", s);
owns_s();
// s no longer accessible here
// Fn, FnMut, FnOnce — closure trait hierarchy
// FnOnce — can be called once (consumes captures); all closures implement this
// FnMut — can be called multiple times with mutation; FnOnce + FnMut
// Fn — can be called any times; Fn implies FnMut implies FnOnce
fn call_once<F: FnOnce() -> String>(f: F) -> String { f() }
fn call_many<F: Fn() -> i32>(f: F) -> i32 { f() + f() }
let s2 = String::from("world");
let greeting = call_once(move || format!("hello {}", s2));
println!("{}", greeting);
let value = 7;
let total = call_many(|| value * 2);
println!("{}", total); // 28
// Returning closures — must use Box<dyn Fn>
fn make_adder(n: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |x| x + n)
}
let add5 = make_adder(5);
println!("{}", add5(10)); // 15
// Closure as function pointer (fn) — when no capture
let f: fn(i32) -> i32 = |x| x * 2; // fn pointer, not closure
println!("{}", f(4)); // 8
}Lifetimes
rust
// Lifetime annotations tell the compiler how long references must stay valid.
// The compiler INFERS most lifetimes; annotations required when ambiguous.
// "a is a lifetime parameter: both inputs and output share lifetime "a
fn longest<"a>(x: &"a str, y: &"a str) -> &"a str {
if x.len() > y.len() { x } else { y }
}
// Struct holding a reference — must annotate lifetime
struct Excerpt<'a> {
part: &'a str,
}
impl<"a> Excerpt<"a> {
fn level(&self) -> i32 { 3 }
// Lifetime elision rules apply — 'b inferred from &self
fn announce(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
// 'static lifetime — reference valid for entire program duration
// String literals are 'static
fn static_str() -> &"static str { "I live forever' }
// Multiple lifetime parameters
fn first_or_second<"a, "b>(first: &"a str, _second: &"b str) -> &'a str {
first
}
// Lifetime bounds on generic types: T must outlive 'a
struct Wrapper<"a, T: "a> {
value: &'a T,
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("longest: {}", result);
}
// result cannot be used here — string2 dropped
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a sentence");
let excerpt = Excerpt { part: first_sentence };
println!("excerpt: {}", excerpt.part);
}
// Lifetime elision rules (compiler applies automatically):
// 1. Each reference parameter gets its own lifetime parameter
// 2. If exactly one input lifetime, assign it to all output lifetimes
// 3. If one of the inputs is &self or &mut self, assign its lifetime to all outputsModules & Visibility
rust
// src/main.rs or src/lib.rs
// Inline module
mod math {
// Private by default — pub to expose
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }
// Nested module
pub mod trig {
pub fn sin(x: f64) -> f64 { x.sin() }
}
// pub(crate) — visible within crate only
pub(crate) fn internal() {}
// pub(super) — visible to parent module
pub(super) fn parent_visible() {}
}
// Bring into scope with use
use math::add;
use math::trig::sin;
// Glob import (use sparingly)
// use math::*;
// Rename with as
use math::sub as subtract;
// Re-export with pub use
pub use math::add as public_add;
fn main() {
println!("{}", add(3, 4));
println!("{}", subtract(10, 4));
println!("{:.4}", sin(1.0));
// Absolute path
let r = math::add(1, 2);
// Nested use paths
// use std::{cmp::Ordering, io};
// use std::io::{self, Write};
}
// --- File-based modules ---
// src/lib.rs:
// pub mod garden; <- loads src/garden.rs or src/garden/mod.rs
//
// src/garden.rs:
// pub mod vegetables; <- loads src/garden/vegetables.rs
//
// src/garden/vegetables.rs:
// pub struct Asparagus {}
//
// Usage:
// use crate::garden::vegetables::Asparagus;
// use super::sibling_module::Thing;Concurrency
rust
use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc; // multi-producer, single-consumer channels
fn main() {
// Spawn a thread — closure must be 'static (move ownership in)
let handle = thread::spawn(|| {
for i in 0..10 {
println!("spawned: {}", i);
thread::sleep(std::time::Duration::from_millis(1));
}
});
for i in 0..5 { println!("main: {}", i); }
handle.join().unwrap(); // wait for thread to finish
// Move data into thread
let v = vec![1, 2, 3];
let h = thread::spawn(move || println!("vector: {:?}", v));
h.join().unwrap();
// Channels — message passing
let (tx, rx) = mpsc::channel();
// Clone tx for multiple producers
let tx2 = tx.clone();
thread::spawn(move || { tx.send(String::from("hello")).unwrap(); });
thread::spawn(move || { tx2.send(String::from("world")).unwrap(); });
// rx is an iterator (blocks until message arrives, stops when all senders dropped)
for received in rx { println!("got: {}", received); }
// Shared state: Arc<Mutex<T>>
// Arc = atomically reference-counted (thread-safe Rc)
// Mutex = mutual exclusion
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let h = thread::spawn(move || {
let mut num = counter.lock().unwrap(); // blocks until lock acquired
*num += 1;
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
println!("counter: {}", *counter.lock().unwrap()); // 10
// RwLock — multiple readers or one writer
use std::sync::RwLock;
let lock = Arc::new(RwLock::new(5));
let r1 = lock.read().unwrap(); // shared read
let r2 = lock.read().unwrap();
println!("r1={} r2={}", *r1, *r2);
drop(r1); drop(r2);
*lock.write().unwrap() = 10; // exclusive write
}Async / Await
rust
// Async requires a runtime — tokio is the most common
// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
use tokio::sync::mpsc;
// async fn returns impl Future<Output = T>
async fn fetch_data(id: u32) -> String {
sleep(Duration::from_millis(10)).await; // .await yields to executor
format!("data-{}", id)
}
// async blocks
async fn process() -> Vec<String> {
// Sequential awaits
let a = fetch_data(1).await;
let b = fetch_data(2).await;
// Concurrent with tokio::join! — both run simultaneously
let (c, d) = tokio::join!(fetch_data(3), fetch_data(4));
vec![a, b, c, d]
}
// tokio::spawn — background task (like thread::spawn but async)
async fn concurrent_tasks() {
let handle1 = tokio::spawn(fetch_data(10));
let handle2 = tokio::spawn(fetch_data(20));
let (r1, r2) = tokio::join!(handle1, handle2);
println!("{} {}", r1.unwrap(), r2.unwrap());
}
// tokio::select! — race multiple futures, take first to complete
async fn race_example() {
tokio::select! {
v = fetch_data(1) => println!("first: {}", v),
v = fetch_data(2) => println!("first: {}", v),
}
}
// Async channels
async fn channel_example() {
let (tx, mut rx) = mpsc::channel::<i32>(32);
tokio::spawn(async move {
for i in 0..5 { tx.send(i).await.unwrap(); }
});
while let Some(msg) = rx.recv().await { println!("received: {}", msg); }
}
// Error handling in async
async fn fallible() -> Result<String, Box<dyn std::error::Error>> {
let data = fetch_data(1).await;
Ok(data.to_uppercase())
}
#[tokio::main]
async fn main() {
let results = process().await;
println!("{:?}", results);
concurrent_tasks().await;
race_example().await;
channel_example().await;
println!("{:?}", fallible().await);
}Macros
rust
// Declarative macros — macro_rules!
macro_rules! say_hello {
() => { println!("Hello!"); };
($name:expr) => { println!("Hello, {}!", $name); };
}
// Macro that creates a HashMap
macro_rules! map {
($($key:expr => $val:expr),* $(,)?) => {{
let mut m = std::collections::HashMap::new();
$( m.insert($key, $val); )*
m
}};
}
// Variadic macro
macro_rules! max {
($x:expr) => { $x };
($x:expr, $($rest:expr),+) => {{
let rest_max = max!($($rest),+);
if $x > rest_max { $x } else { rest_max }
}};
}
fn main() {
say_hello!();
say_hello!("Alice");
let m = map!{ 'a' => 1, 'b' => 2, 'c' => 3 };
println!("{:?}", m);
println!("max: {}", max!(3, 1, 4, 1, 5, 9, 2, 6)); // 9
// Built-in macros
println!("formatted {}", 42); // print to stdout with newline
eprintln!("error {}", "oops"); // print to stderr
let s = format!("hello {}", "world");
let v = vec![1, 2, 3];
assert!(1 + 1 == 2);
assert_eq!(2 + 2, 4, "math is broken");
assert_ne!(1, 2);
let x: Option<i32> = None;
// panic!("something went wrong"); // explicit panic
// dbg! — prints file/line/value, returns the value
let a = dbg!(2 + 3) * dbg!(4); // [src/main.rs:N] 2 + 3 = 5 ...
println!("a={}", a);
// todo!, unimplemented!, unreachable!
// fn stub() -> i32 { todo!() } // panics with "not yet implemented"
// include_str!, include_bytes! — embed files at compile time
// const DATA: &str = include_str!("../data.txt");
// concat!, stringify!, env!, cfg!
let s2 = concat!("foo", "bar", 42); // "foobar42"
let is_debug = cfg!(debug_assertions);
println!("s2={} debug={}", s2, is_debug);
}Smart Pointers
rust
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::Arc;
// Box<T> — heap allocation, single owner
fn main() {
let b = Box::new(5); // 5 stored on heap
println!("b = {}", b); // auto-deref
// Box enables recursive types (otherwise infinite size)
#[derive(Debug)]
enum List { Cons(i32, Box<List>), Nil }
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{:?}", list);
// Rc<T> — reference-counted, single-threaded shared ownership
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a); // increments ref count (strong)
let c = Rc::clone(&a);
println!("count={} a={:?}", Rc::strong_count(&a), a);
drop(c);
println!("count after drop={}", Rc::strong_count(&a)); // 2
// RefCell<T> — interior mutability (borrow rules checked at runtime)
let data = RefCell::new(vec![1, 2, 3]);
{
let mut borrow = data.borrow_mut(); // panics if already borrowed
borrow.push(4);
}
println!("{:?}", data.borrow()); // [1, 2, 3, 4]
// Rc<RefCell<T>> — shared mutable state in single-threaded code
let shared = Rc::new(RefCell::new(0));
let clone1 = Rc::clone(&shared);
let clone2 = Rc::clone(&shared);
*clone1.borrow_mut() += 10;
*clone2.borrow_mut() += 20;
println!("shared: {}", shared.borrow()); // 30
// Arc<T> — atomic ref count, thread-safe version of Rc
// Use Arc<Mutex<T>> for shared mutable state across threads (see concurrency section)
let arc = Arc::new(vec![1, 2, 3]);
let arc2 = Arc::clone(&arc);
std::thread::spawn(move || println!("thread: {:?}", arc2)).join().unwrap();
// Weak<T> — non-owning reference (prevents reference cycles)
use std::rc::Weak;
let strong = Rc::new(5);
let weak: Weak<i32> = Rc::downgrade(&strong);
println!("weak: {:?}", weak.upgrade()); // Some(5)
drop(strong);
println!("weak after drop: {:?}", weak.upgrade()); // None
}Advanced Traits
rust
use std::fmt;
// Trait objects — dynamic dispatch via vtable
trait Draw { fn draw(&self); }
struct Screen { components: Vec<Box<dyn Draw>> }
impl Screen {
fn render(&self) { for c in &self.components { c.draw(); } }
}
struct Button { label: String }
struct Image { src: String }
impl Draw for Button { fn draw(&self) { println!("Button: {}", self.label); } }
impl Draw for Image { fn draw(&self) { println!("Image: {}", self.src); } }
// Associated types — cleaner than generic parameters when there is one natural type
trait Converter {
type Output;
fn convert(&self) -> Self::Output;
}
struct Fahrenheit(f64);
impl Converter for Fahrenheit {
type Output = f64;
fn convert(&self) -> f64 { (self.0 - 32.0) * 5.0 / 9.0 }
}
// Trait with where clause
trait Printable where Self: fmt::Display + fmt::Debug {}
impl<T: fmt::Display + fmt::Debug> Printable for T {}
// Supertrait — require another trait
trait Animal: fmt::Display {
fn name(&self) -> &str;
fn sound(&self) -> &str;
fn description(&self) -> String { format!("{} says {}", self.name(), self.sound()) }
}
// Blanket implementations — implement trait for all types that satisfy bounds
trait DoubleDisplay: fmt::Display {
fn double_display(&self) -> String { format!("{}{}", self, self) }
}
impl<T: fmt::Display> DoubleDisplay for T {} // auto-impl for all Display types
// Newtype pattern for orphan rule
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let screen = Screen {
components: vec![
Box::new(Button { label: String::from("OK") }),
Box::new(Image { src: String::from("logo.png") }),
]
};
screen.render();
println!("celsius: {:.1}", Fahrenheit(212.0).convert()); // 100.0
let nums = vec![String::from("a"), String::from("b")];
let w = Wrapper(nums);
println!("{}", w); // [a, b]
println!("double: {}", 42.double_display()); // 4242
}Pattern Matching
rust
fn main() {
// Destructuring structs
struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: -10 };
let Point { x, y } = p;
println!("x={} y={}", x, y);
// Destructuring enums
enum Message {
Move { x: i32, y: i32 },
Write(String),
Color(u8, u8, u8),
Quit,
}
let msg = Message::Move { x: 10, y: 20 };
match msg {
Message::Move { x, y } => println!("move to {},{}", x, y),
Message::Write(text) => println!("write: {}", text),
Message::Color(r, g, b) => println!("color {},{},{}", r, g, b),
Message::Quit => println!("quit"),
}
// Nested destructuring
let ((a, b), c) = ((1, 2), 3);
// Tuple in match
let pair = (true, 42);
match pair {
(true, n) if n > 0 => println!("positive true: {}", n),
(false, _) => println!("false"),
_ => println!("other"),
}
// Range patterns
let n = 13;
match n {
1..=12 => println!("less than 13"),
13 => println!("thirteen"),
14..=i32::MAX => println!("big"),
_ => println!("negative"),
}
// @ bindings — capture and test
let num = 7;
match num {
n @ 1..=10 => println!("1..=10: n={}", n),
n @ 11.. => println!("big: n={}", n),
_ => println!("other"),
}
// if let — single pattern (ignores non-matching)
let config_max = Some(3u8);
if let Some(max) = config_max { println!("max={}", max); }
// if let + else
let val: Result<i32, &str> = Ok(42);
if let Ok(n) = val { println!("ok: {}", n); } else { println!("error"); }
// while let
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() { println!("popped: {}", top); }
// Ignore with ..
struct Point3 { x: i32, y: i32, z: i32 }
let p3 = Point3 { x: 1, y: 2, z: 3 };
let Point3 { x, .. } = p3; // ignore y and z
// Multiple patterns with |
let c = 'a';
match c {
'a' | 'e' | 'i' | 'o' | 'u' => println!("vowel"),
'a'..='z' => println!("consonant"),
_ => println!("other"),
}
}Testing
rust
// Unit tests live in the same file, in a test module
// Run: cargo test
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
pub fn greet(name: &str) -> String { format!("Hello, {}!", name) }
#[cfg(test)] // compiled only during "cargo test"
mod tests {
use super::*; // import everything from parent module
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide() {
assert_eq!(divide(10.0, 2.0), Some(5.0));
assert_eq!(divide(1.0, 0.0), None);
}
#[test]
fn test_greet() {
let result = greet("Alice");
assert!(result.contains("Alice"));
assert_eq!(result, "Hello, Alice!");
assert_ne!(result, "Hello, Bob!");
}
#[test]
#[should_panic(expected = "divide by zero")]
fn test_panics() {
panic!("divide by zero");
}
#[test]
fn test_result() -> Result<(), String> {
// Tests can return Result — Err causes failure
let n: i32 = "42".parse().map_err(|e| format!("parse error: {}", e))?;
assert_eq!(n, 42);
Ok(())
}
#[test]
#[ignore = "slow test, run with --include-ignored"]
fn test_slow() {
std::thread::sleep(std::time::Duration::from_secs(2));
}
}
// Integration tests go in tests/ directory (separate crate, test public API only)
// tests/integration_test.rs:
// use my_crate::add;
// #[test]
// fn it_adds() { assert_eq!(add(2, 2), 4); }
// Doc tests — code in /// doc comments is compiled and run
/// Adds two numbers.
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add_doc(a: i32, b: i32) -> i32 { a + b }
// cargo test -- --nocapture (show println! output)
// cargo test -- --test-threads=1 (sequential)
// cargo test test_add (filter by name)Cargo & Tooling
rust
# Cargo.toml — project manifest
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Alice <alice@example.com>"]
description = "A sample Rust project"
license = "MIT"
[dependencies]
# Specific version
serde = "1.0"
# Version range
tokio = ">= 1.0, < 2.0"
# Git dependency
# my_lib = { git = "https://github.com/user/my_lib", branch = "main" }
# Path dependency (local)
# utils = { path = "../utils" }
# Optional dependency (enabled by features)
# reqwest = { version = "0.11", optional = true }
[dependencies.serde]
version = "1.0"
features = ["derive"] # enable serde macros
[dev-dependencies] # only for tests and benchmarks
criterion = "0.5"
[build-dependencies] # only for build.rs
cc = "1.0"
[features]
default = ["std"]
std = []
async = ["tokio"]
full = ["std", "async"]
[profile.dev]
opt-level = 0 # fast compile, slow binary
debug = true
[profile.release]
opt-level = 3 # slow compile, fast binary
lto = true # link-time optimisation
strip = true # strip debug symbols
# Workspace — monorepo with multiple crates
# [workspace]
# members = ["crate_a", "crate_b", "crate_c"]
# --- Common cargo commands ---
# cargo new my_project --bin create binary project
# cargo new my_lib --lib create library project
# cargo build compile debug
# cargo build --release compile release
# cargo run compile + run
# cargo run -- arg1 arg2 pass args to binary
# cargo test run all tests
# cargo test -- --nocapture show stdout
# cargo check type-check without linking (fast)
# cargo clippy linter — catches common mistakes
# cargo fmt auto-format code (rustfmt)
# cargo doc --open build + open API docs
# cargo add serde --features derive add dependency
# cargo update update Cargo.lock
# cargo tree visualise dependency tree
# cargo bench run benchmarks
# cargo publish publish to crates.ioBest Practices
Ownership & Borrowing
rust
// Prefer borrowing over cloning — avoid unnecessary heap allocations
fn process(data: &[i32]) -> i32 { // borrow slice, not Vec
data.iter().sum()
}
fn get_name(user: &User) -> &str { // return borrow, not owned String
&user.name
}
// Use owned types in structs; borrow in function signatures
struct Config { host: String, port: u16 } // owns its data
// Split borrows — borrow disjoint fields simultaneously
struct Counter { value: i32, step: i32 }
impl Counter {
fn increment(&mut self) {
let step = self.step; // copy before mutable borrow
self.value += step;
}
}
// Avoid fighting the borrow checker — restructure rather than using unsafe
// BAD pattern (compile error):
// fn bad(v: &mut Vec<i32>) -> &i32 { v.push(1); &v[0] } // push invalidates ref
// GOOD: separate the borrow from the mutation
fn good(v: &mut Vec<i32>) -> i32 {
v.push(1);
v[0] // return copy
}
// Use indices instead of references when mutating a collection you're iterating
fn remove_negatives(v: &mut Vec<i32>) {
v.retain(|&x| x >= 0); // retain is idiomatic
}
// Cow<str> for "sometimes owned, sometimes borrowed" data
use std::borrow::Cow;
fn maybe_uppercase<"a>(s: &"a str, upper: bool) -> Cow<'a, str> {
if upper { Cow::Owned(s.to_uppercase()) }
else { Cow::Borrowed(s) }
}Error Handling
rust
use std::fmt;
// Define domain error types — avoid String errors
#[derive(Debug)]
pub enum ServiceError {
NotFound(String),
Unauthorized,
Database(sqlx::Error), // wrap third-party errors
}
impl fmt::Display for ServiceError { /* ... */ }
impl std::error::Error for ServiceError {}
// Use thiserror crate for less boilerplate
// [dependencies] thiserror = '1'
// #[derive(thiserror::Error, Debug)]
// pub enum MyError {
// #[error("not found: {0}")] NotFound(String),
// #[error("db error: {0}")] Db(#[from] sqlx::Error),
// }
// Use anyhow for application code (vs. library code)
// [dependencies] anyhow = '1'
// fn run() -> anyhow::Result<()> {
// let n: i32 = "42".parse().context("parsing failed")?;
// Ok(())
// }
// Never panic in library code — return Result or Option instead
// Reserve unwrap() for:
// 1. Tests
// 2. Truly impossible cases (document WHY it cannot fail)
// 3. Prototypes
// Propagate errors with ? — keeps happy path clean
fn load_and_parse(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
let contents = std::fs::read_to_string(path)?;
let config: Config = serde_json::from_str(&contents)?;
Ok(config)
}
// Centralize error conversion at crate boundaries
// Internal functions can return specific errors
// Public API returns a unified error type
// Use map_err to provide context
fn parse_port(s: &str) -> Result<u16, String> {
s.parse::<u16>().map_err(|e| format!("invalid port {}: {}", s, e))
}
struct Config;Traits & Generics
rust
// Prefer impl Trait in function signatures over Box<dyn Trait> when possible
// impl Trait = static dispatch (monomorphized, zero overhead)
// Box<dyn Trait> = dynamic dispatch (vtable, heap alloc)
fn process_static(iter: impl Iterator<Item = i32>) -> i32 { iter.sum() }
fn process_dynamic(iter: Box<dyn Iterator<Item = i32>>) -> i32 { iter.sum() }
// Use trait bounds to constrain generics precisely
use std::fmt;
fn print_twice<T: fmt::Display>(val: &T) {
println!("{}", val);
println!("{}", val);
}
// Prefer associated types over generic parameters for unique relationships
trait Container {
type Item; // there is only one natural Item type per Container
fn first(&self) -> Option<&Self::Item>;
}
// Use Default trait for sensible zero-values
#[derive(Debug, Default)]
struct Config { timeout: u32, retries: u8, verbose: bool }
let c = Config { verbose: true, ..Config::default() };
// Blanket implementations extend functionality without modifying types
trait Summary { fn summary(&self) -> String; }
// implement for all Vecs whose elements implement Display
impl<T: fmt::Display> Summary for Vec<T> {
fn summary(&self) -> String {
self.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", ")
}
}
// Derive common traits — less manual implementation
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct UserId(u64);
// Now usable as HashMap key, can be cloned, printed with {:?}, compared with ==Concurrency
rust
use std::sync::{Arc, Mutex};
use std::thread;
// Prefer message passing over shared state (channels are often simpler)
use std::sync::mpsc;
fn producer_consumer() {
let (tx, rx) = mpsc::channel::<String>();
let producer = thread::spawn(move || {
for i in 0..10 { tx.send(format!("item-{}", i)).unwrap(); }
});
let consumer = thread::spawn(move || {
for msg in rx { println!("consumed: {}", msg); }
});
producer.join().unwrap();
consumer.join().unwrap();
}
// Minimise lock contention — hold locks for the shortest possible time
fn increment_counter(counter: &Arc<Mutex<i32>>) {
let mut guard = counter.lock().unwrap();
*guard += 1;
// guard dropped here — NOT after long computation
}
// Avoid deadlocks — always acquire locks in the same order
// Consider using a single struct with multiple fields to avoid multiple locks
// Use Rayon for data parallelism (CPU-bound)
// [dependencies] rayon = '1'
// use rayon::prelude::*;
// let sum: i32 = (0..1_000_000).into_par_iter().sum();
// Use atomic types for simple counters (faster than Mutex)
use std::sync::atomic::{AtomicUsize, Ordering};
static REQUEST_COUNT: AtomicUsize = AtomicUsize::new(0);
fn handle_request() { REQUEST_COUNT.fetch_add(1, Ordering::Relaxed); }
fn get_request_count() -> usize { REQUEST_COUNT.load(Ordering::SeqCst) }
// Send + Sync bounds ensure thread-safety at compile time
// If your type contains only Send + Sync fields, it auto-derives them
// Arc<Mutex<T>> is Send + Sync even when T is not Sync alonePerformance
rust
// Profile before optimizing — cargo flamegraph, cargo bench (criterion)
// Pre-allocate collections when size is known
fn build_vec(n: usize) -> Vec<i32> {
let mut v = Vec::with_capacity(n); // single allocation
for i in 0..n as i32 { v.push(i); }
v
}
// Use iterators — they compose and often get optimised to tight loops
fn sum_squares(data: &[f64]) -> f64 {
data.iter().map(|&x| x * x).sum()
}
// Avoid allocations in hot paths — prefer &str over String, slices over Vecs
fn count_vowels(s: &str) -> usize {
s.chars().filter(|c| "aeiouAEIOU".contains(*c)).count()
}
// String building — use a reusable buffer
fn build_csv(rows: &[Vec<String>]) -> String {
let mut out = String::new(); // or with_capacity estimate
for row in rows {
out.push_str(&row.join(','));
out.push('\n');
}
out
}
// Use #[inline] for small frequently-called functions
#[inline(always)]
fn fast_max(a: i32, b: i32) -> i32 { if a > b { a } else { b } }
// Release profile optimizations (Cargo.toml)
// [profile.release]
// opt-level = 3
// lto = "fat" -- link-time optimization across crates
// codegen-units = 1 -- single codegen unit (slower compile, faster binary)
// Avoid unnecessary Box/Rc/Arc — prefer stack values
// Use SmallVec (smallvec crate) when Vec usually has <= N elements
// Use ahash/FxHashMap instead of HashMap when hash quality matters less than speedTooling & Ecosystem
rust
# Essential toolchain commands
# Install / manage Rust versions
rustup update stable # update stable toolchain
rustup show # list installed toolchains
rustup default nightly # switch default to nightly
rustup override set 1.70.0 # pin version for current directory
rustup target add wasm32-unknown-unknown # cross-compile target
rustup component add clippy rustfmt rust-analyzer
# Linting and formatting (enforce in CI)
cargo clippy -- -D warnings # fail on any warning
cargo clippy --fix # auto-fix some lints
cargo fmt # format code
cargo fmt -- --check # verify (CI mode)
# Useful Clippy lints to enable in code or .clippy.toml
// #![deny(clippy::all)]
// #![warn(clippy::pedantic)]
// #![allow(clippy::too_many_arguments)]
# Dependency management
cargo outdated # list outdated deps (cargo-outdated)
cargo audit # check for security advisories (cargo-audit)
cargo tree # visualise dependency tree
cargo machete # find unused dependencies
cargo deny check # license + advisory policy (cargo-deny)
# Documentation
cargo doc --no-deps --open # build own crate's docs
/// Three-slash comments produce doc pages
/// ```
/// # Examples
/// let x = my_crate::add(1, 2);
/// assert_eq!(x, 3);
/// ```
# Recommended crates by category
# Error handling: thiserror (libraries), anyhow (binaries)
# Async: tokio, async-std
# HTTP client: reqwest
# Serialization: serde + serde_json / serde_yaml / bincode
# CLI: clap (derive feature), argh
# Logging: tracing, log + env_logger
# Testing: pretty_assertions, mockall, insta (snapshots)
# Databases: sqlx (async, compile-time checked queries)
# Data parallel: rayon