Rust
SystemsOverview
Systems programming language focused on safety, speed, and concurrency. Guarantees memory safety without a garbage collector.
Resources
Popular learning and reference links:
Installation & Getting Started
Install via rustup, the official Rust toolchain installer. It manages Rust versions, components, and targets.
# Install Rust via rustup (recommended)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# macOS (Homebrew — not recommended, use rustup)
brew install rust
# Verify installation
rustc --version
cargo --version
# Update Rust
rustup update
# Install a specific toolchain
rustup install nightly
rustup default stable
# No built-in REPL — but evcxr is a popular Rust REPL
cargo install evcxr_repl
evcxr
# Rust Playground — try in the browser
# https://play.rust-lang.org/
# Run a file directly
rustc hello.rs && ./hello
# Run via Cargo (preferred)
cargo run
# Quick script with cargo-script
cargo install cargo-script
cargo script hello.rs Project Scaffolding
Cargo creates the full project structure with one command.
# Create a new binary project
cargo new my-project
cd my-project
# Create a library project
cargo new my-lib --lib
# Project structure created:
# my-project/
# Cargo.toml
# src/
# main.rs (or lib.rs)
# Run the project
cargo run
# Build for release
cargo build --release
# Run tests
cargo test Package Management
Cargo is Rust’s built-in package manager and build tool. Packages are called “crates”.
# Add a dependency
cargo add serde
# Add with features
cargo add serde --features derive
# Add a dev dependency
cargo add --dev tokio-test
# Remove a dependency
cargo remove serde
# Update dependencies
cargo update
# Search for crates
cargo search json Tooling & Formatter/Linter
Rust ships with rustfmt and clippy as official tools. The compiler itself is an excellent linter.
# rustfmt — official formatter
rustfmt src/main.rs
cargo fmt # Format entire project
cargo fmt -- --check # Check without modifying
# Clippy — official linter (hundreds of lints)
cargo clippy
cargo clippy -- -W clippy::pedantic # Stricter lints
cargo clippy --fix # Auto-fix suggestions
# cargo check — fast type/borrow checking (no codegen)
cargo check
# rustfmt.toml
max_width = 100
edition = "2021"
use_field_init_shorthand = true
# Clippy config in Cargo.toml
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
needless_pass_by_value = "allow"
# rust-analyzer — language server for all editors
# Provides inline errors, completions, refactoring,
# hover docs, and inlay type hints Build & Compile Model
Ahead-of-time compiled to native binary via LLVM. Rust produces highly optimized machine code with zero-cost abstractions.
# Debug build (fast compile, slow runtime)
cargo build
./target/debug/myapp
# Release build (slow compile, fast runtime)
cargo build --release
./target/release/myapp
# Compile + run
cargo run
cargo run --release
# Cross-compilation
rustup target add x86_64-unknown-linux-musl
cargo build --target x86_64-unknown-linux-musl --release
# Check without building (fastest feedback)
cargo check
# Output: native binary, or .rlib/.so/.a for libraries
Execution model:
- Source → HIR → MIR (borrow checking) → LLVM IR → Machine code
- No runtime, no GC — zero-cost abstractions
- Compile times are slower than Go/C due to deep optimization + borrow checker
- Incremental compilation and
sccachehelp with rebuild speed - Can target WASM:
cargo build --target wasm32-unknown-unknown
Libraries & Frameworks
Rust’s ecosystem is centered on crates.io. Strong focus on performance, safety, and systems programming.
Web Frameworks - Actix Web, Axum, Rocket, Warp, Poem
Async Runtime - Tokio, async-std, smol
Serialization - serde, serde_json, bincode, postcard
Databases - SQLx, Diesel, SeaORM, rusqlite
CLI - clap, structopt, dialoguer, indicatif, ratatui (TUI)
Error Handling - anyhow, thiserror, eyre, miette
HTTP Clients - reqwest, hyper, ureq
Logging - tracing, log, env_logger
Concurrency - rayon, crossbeam, tokio, parking_lot
Crypto - ring, rustls, rust-crypto
WASM - wasm-bindgen, wasm-pack, Leptos, Yew, Dioxus
Embedded - embedded-hal, embassy, probe-rs
Testing
Rust has built-in testing support — no external framework needed. Tests live alongside code or in a tests/ directory.
// Unit tests — in the same file as the code
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
#[should_panic(expected = "overflow")]
fn test_overflow() {
let _: u8 = 255u8 + 1; // panics in debug mode
}
}
// Integration tests — in tests/ directory
// tests/integration_test.rs
use my_crate::add;
#[test]
fn it_adds() {
assert_eq!(add(2, 3), 5);
}
# Run tests
cargo test # All tests
cargo test test_add # Specific test
cargo test -- --nocapture # Show println! output
cargo test --doc # Doc tests only
cargo bench # Benchmarks (nightly) Debugging
Use LLDB or GDB for debugging. The compiler provides excellent error messages. dbg!() macro for quick inspection.
// dbg! macro — print expression + value + file:line
let x = 42;
dbg!(x); // [src/main.rs:3] x = 42
dbg!(x * 2); // [src/main.rs:4] x * 2 = 84
// println! for basic output
println!("value: {:?}", my_struct); // Debug format
println!("value: {:#?}", my_struct); // Pretty-print
// eprintln! — print to stderr
eprintln!("error: {}", msg);
// tracing — structured logging
use tracing::{info, warn, error, debug, instrument};
#[instrument]
fn process(id: u32) {
info!(id, "processing");
debug!(?some_value, "details");
error!(%err, "failed");
}
# LLDB (default on macOS, works on Linux)
lldb target/debug/myapp
# b main.rs:42 (breakpoint)
# r (run)
# n (next)
# s (step into)
# p variable (print)
# bt (backtrace)
# GDB
gdb target/debug/myapp
# Same commands but GDB syntax
# VS Code — CodeLLDB extension (recommended)
# launch.json:
{
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/myapp",
"args": [],
"cwd": "${workspaceFolder}"
}
# Set breakpoints, inspect variables, view call stack
# Compiler errors are the first line of debugging
# Rust's error messages include:
# - Exact location, suggested fixes
# - Borrow checker explanations
# cargo clippy for additional warnings Variables
Variables are immutable by default. Use mut for mutability.
// Immutable (default)
let name = "Alice";
let age = 30;
// Mutable
let mut count = 0;
count += 1;
// Type annotation
let x: i32 = 42;
let pi: f64 = 3.14;
// Shadowing (re-declare with same name)
let x = 5;
let x = x + 1; // x is now 6
let x = "now a string"; // different type
// Constants (must be typed, compile-time)
const MAX_SIZE: usize = 100; Types
Statically typed with powerful enums, structs, traits, and generics.
// Primitives
let s: &str = "hello";
let n: i32 = 42;
let f: f64 = 3.14;
let b: bool = true;
let c: char = 'a';
// Structs
struct User {
name: String,
age: u32,
email: Option<String>,
}
// Enums (algebraic data types)
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
// Vectors and HashMaps
let nums: Vec<i32> = vec![1, 2, 3];
let mut map = HashMap::new();
map.insert("key", "value");
// Option and Result
let maybe: Option<i32> = Some(42);
let result: Result<i32, String> = Ok(42); Data Structures
Rich standard library: Vec, HashMap, HashSet, BTreeMap, VecDeque, LinkedList. All generic and ownership-aware.
// Vec — growable array (most common)
let mut v = vec![1, 2, 3];
v.push(4);
v.pop(); // Some(4)
v[0]; // 1
v.len(); // 3
v.contains(&2); // true
let slice = &v[1..3]; // &[2, 3]
// HashMap
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("alice", 95);
map.insert("bob", 87);
map.get("alice"); // Some(&95)
map.contains_key("bob");
map.entry("charlie").or_insert(0);
// HashSet
use std::collections::HashSet;
let mut set: HashSet<i32> = [1, 2, 3].into();
set.insert(4);
set.contains(&2); // true
let intersection = &set & &other_set;
// Iterators (lazy, chainable)
let sum: i32 = v.iter().filter(|&&x| x > 1).sum();
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
// Tuples
let pair: (String, i32) = ("age".into(), 30);
let (key, value) = pair;
// BTreeMap — sorted keys
use std::collections::BTreeMap;
let mut bt = BTreeMap::new();
bt.insert(3, "c");
bt.insert(1, "a");
// Iteration is in key order
// VecDeque — double-ended queue
use std::collections::VecDeque;
let mut dq = VecDeque::from([1, 2, 3]);
dq.push_front(0);
dq.push_back(4); Functions
Functions use fn. The last expression is the return value (no semicolon).
// Basic function
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Multiple parameters
fn add(a: i32, b: i32) -> i32 {
a + b
}
// Closures
let add = |a: i32, b: i32| -> i32 { a + b };
let double = |x| x * 2;
// Generic function
fn first<T>(items: &[T]) -> Option<&T> {
items.first()
}
// Methods (impl block)
impl User {
fn new(name: String, age: u32) -> Self {
Self { name, age, email: None }
}
fn display(&self) -> String {
format!("{} ({})", self.name, self.age)
}
} Conditionals
if/else is an expression. match is powerful pattern matching.
// If / else (is an expression)
let label = if x > 0 {
"positive"
} else if x == 0 {
"zero"
} else {
"negative"
};
// Match (exhaustive pattern matching)
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
}
// Match with guards
match age {
0..=12 => "child",
13..=17 => "teen",
18..=64 => "adult",
_ => "senior",
}
// If let (single pattern)
if let Some(value) = maybe_value {
println!("Got: {}", value);
}
// Let-else
let Some(x) = optional else {
return;
}; Loops
for, while, and loop (infinite). Iterators are idiomatic.
// For loop with range
for i in 0..5 {
println!("{}", i);
}
// Iterate over a collection
let items = vec!["a", "b", "c"];
for item in &items {
println!("{}", item);
}
// While loop
let mut n = 0;
while n < 3 {
n += 1;
}
// Infinite loop with break
loop {
break;
}
// Loop with return value
let result = loop {
if condition {
break 42;
}
};
// Iterator methods
let squares: Vec<i32> = (0..10).map(|x| x * x).collect();
let sum: i32 = vec![1, 2, 3].iter().sum(); Generics & Type System
Powerful generics with trait bounds. Monomorphized at compile time (zero-cost). Supports associated types, lifetimes, and const generics.
// Generic function
fn identity<T>(value: T) -> T { value }
// Trait bounds — constrain type parameters
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut max = &list[0];
for item in &list[1..] {
if item > max { max = item; }
}
max
}
// Multiple bounds
fn print_debug<T: std::fmt::Debug + Clone>(item: T) {
println!("{:?}", item.clone());
}
// Where clause (cleaner for complex bounds)
fn process<T, U>(t: T, u: U) -> String
where
T: std::fmt::Display + Clone,
U: std::fmt::Debug,
{
format!("{} {:?}", t, u)
}
// Generic structs
struct Wrapper<T> {
value: T,
}
impl<T: std::fmt::Display> Wrapper<T> {
fn show(&self) { println!("{}", self.value); }
}
// Associated types (in traits)
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
// Lifetime generics
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Const generics
fn create_array<const N: usize>() -> [i32; N] {
[0; N]
}
let arr = create_array::<5>(); // [0, 0, 0, 0, 0]
// Trait objects (dynamic dispatch)
fn print_all(items: &[&dyn std::fmt::Display]) {
for item in items { println!("{}", item); }
} Inheritance & Composition
Rust has no class inheritance. Uses traits for shared behavior, generics for polymorphism, and composition via struct fields.
// Traits (shared behavior)
trait Speak {
fn speak(&self) -> String;
}
struct Dog { name: String }
impl Speak for Dog {
fn speak(&self) -> String {
format!("{} barks", self.name)
}
}
// Default implementations
trait Greet {
fn name(&self) -> &str;
fn greet(&self) -> String {
format!("Hello, {}!", self.name())
}
}
// Trait inheritance (supertraits)
trait Animal: Speak + std::fmt::Display {}
// Composition via fields
struct PetOwner {
pet: Dog,
name: String,
}
// Generic bounds (static dispatch)
fn announce(speaker: &impl Speak) {
println!("{}", speaker.speak());
}
// Trait objects (dynamic dispatch)
fn announce_dyn(speaker: &dyn Speak) {
println!("{}", speaker.speak());
}
// Derive macros (auto-implement traits)
#[derive(Debug, Clone, PartialEq)]
struct Point { x: f64, y: f64 } Functional Patterns
Iterators, closures, and Option/Result combinators are core to idiomatic Rust. Zero-cost abstractions make functional style performant.
// Closures
let double = |x: i32| x * 2;
let add = |a: i32, b: i32| a + b;
// Iterator combinators
let nums = vec![1, 2, 3, 4, 5];
let squared: Vec<i32> = nums.iter().map(|x| x * x).collect();
let evens: Vec<&i32> = nums.iter().filter(|x| *x % 2 == 0).collect();
let sum: i32 = nums.iter().sum();
// Chaining
let result: i32 = (0..100)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.take(10)
.sum();
// Option combinators
let name: Option<String> = Some("Alice".into());
let upper = name.map(|n| n.to_uppercase());
let len = name.and_then(|n| if n.is_empty() { None } else { Some(n.len()) });
let fallback = name.unwrap_or("default".into());
// Result combinators
let parsed: Result<i32, _> = "42".parse();
let doubled = parsed.map(|n| n * 2);
let chained = parsed.and_then(|n| if n > 0 { Ok(n) } else { Err("negative") });
// Higher-order functions
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x) } Concurrency
Fearless concurrency — the borrow checker prevents data races at compile time. Supports threads, async/await, and message passing.
// Threads
use std::thread;
let handle = thread::spawn(|| {
println!("Hello from thread!");
});
handle.join().unwrap();
// Channels (mpsc — multiple producer, single consumer)
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("hello").unwrap();
});
println!("{}", rx.recv().unwrap());
// Shared state with Mutex + Arc
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
*counter.lock().unwrap() += 1;
}));
}
for h in handles { h.join().unwrap(); }
// async/await (with Tokio runtime)
use tokio;
#[tokio::main]
async fn main() {
let (a, b) = tokio::join!(
fetch_data("url_a"),
fetch_data("url_b"),
);
// Spawn concurrent tasks
tokio::spawn(async { do_work().await });
}
// Rayon — data parallelism
use rayon::prelude::*;
let sum: i32 = (0..1000).into_par_iter().sum(); Modules & Imports
Rust uses a module tree rooted at lib.rs or main.rs. Modules can be inline or in separate files. Crates are the unit of compilation and distribution.
// Declare a module (looks for math.rs or math/mod.rs)
mod math;
// Use items from a module
use crate::math::add;
use crate::math::{add, subtract};
// Use with alias
use std::collections::HashMap as Map;
// Glob import (discouraged)
use std::io::prelude::*;
// Re-export
pub use crate::math::add;
// Nested paths
use std::{
fs::File,
io::{self, Read, Write},
};
// External crate (from Cargo.toml)
use serde::{Serialize, Deserialize};
use tokio::runtime::Runtime;
// Inline module
mod inner {
pub fn helper() -> i32 { 42 }
}
// Visibility modifiers
pub fn public() {} // public
pub(crate) fn crate_only() {} // crate-visible
fn private() {} // module-private
pub(super) fn parent_only() {} // parent module Error Handling
No exceptions. Uses Result<T, E> and Option<T> types with the ? operator.
// Result type
fn read_file(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?; // ? propagates error
Ok(content)
}
// Handling Results
match result {
Ok(value) => println!("Got: {}", value),
Err(e) => eprintln!("Error: {}", e),
}
// unwrap (panics on error — use sparingly)
let value = result.unwrap();
let value = result.unwrap_or(default);
let value = result.unwrap_or_else(|e| handle(e));
// Custom error types
#[derive(Debug)]
enum AppError {
NotFound(String),
ParseError(std::num::ParseIntError),
}
impl From<std::num::ParseIntError> for AppError {
fn from(e: std::num::ParseIntError) -> Self {
AppError::ParseError(e)
}
} Memory Management
Ownership and borrowing — no GC. Memory is freed deterministically when the owner goes out of scope. The borrow checker enforces safety at compile time.
// Ownership — each value has exactly one owner
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2
// println!("{}", s1); // ERROR: s1 is no longer valid
// Clone — explicit deep copy
let s3 = s2.clone();
// Borrowing — references without ownership
fn print_len(s: &String) { // Immutable borrow
println!("{}", s.len());
}
fn append(s: &mut String) { // Mutable borrow
s.push_str(" world");
}
// Rules:
// 1. Many immutable borrows OR one mutable borrow
// 2. References must always be valid (no dangling)
// Drop trait — deterministic cleanup (like RAII)
struct FileHandle { fd: i32 }
impl Drop for FileHandle {
fn drop(&mut self) {
close_fd(self.fd); // Runs when value goes out of scope
}
}
{
let f = FileHandle { fd: 42 };
// ... use f ...
} // f.drop() called here automatically
// Smart pointers
use std::rc::Rc; // Reference counted (single-thread)
use std::sync::Arc; // Atomic reference counted (multi-thread)
use std::cell::RefCell; // Interior mutability
let shared = Rc::new(vec![1, 2, 3]);
let clone = Rc::clone(&shared); // Cheap (increments count)
// Box — heap allocation
let boxed: Box<dyn Trait> = Box::new(MyStruct {});
// No GC pauses, no runtime overhead
// Memory bugs caught at compile time, not runtime Performance Profiling
Use cargo bench for benchmarks, perf/flamegraph for CPU profiling, and DHAT/Valgrind for memory.
// Criterion.rs — statistical benchmarking
// Cargo.toml: criterion = { version = "0.5", features = ["html_reports"] }
use criterion::{criterion_group, criterion_main, Criterion};
fn bench_sort(c: &mut Criterion) {
c.bench_function("sort 1000", |b| {
b.iter(|| {
let mut v: Vec<i32> = (0..1000).rev().collect();
v.sort();
})
});
}
criterion_group!(benches, bench_sort);
criterion_main!(benches);
# Built-in benchmarks (nightly only)
cargo bench
# Criterion benchmarks (stable Rust)
cargo bench # with criterion in Cargo.toml
# Flame graphs
cargo install flamegraph
cargo flamegraph # Generates flamegraph.svg
# perf (Linux)
perf record --call-graph dwarf ./target/release/myapp
perf report
# Valgrind / Cachegrind
valgrind --tool=callgrind ./target/release/myapp
# Visualize with kcachegrind
# DHAT — heap profiler
valgrind --tool=dhat ./target/release/myapp
# cargo-instruments (macOS)
cargo install cargo-instruments
cargo instruments -t "CPU Profiler"
# Compile time profiling
cargo build --timings # HTML report of compile times Interop
Excellent C FFI (zero overhead). Rust can call C and be called from C. wasm-bindgen for JavaScript interop.
// Call C from Rust
extern "C" {
fn sqrt(x: f64) -> f64;
fn printf(fmt: *const i8, ...) -> i32;
}
fn main() {
unsafe {
let result = sqrt(16.0);
println!("{}", result); // 4.0
}
}
// Expose Rust to C
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
// Build: cargo build --release (produces .so/.dylib/.dll)
// bindgen — auto-generate Rust bindings from C headers
// build.rs:
// bindgen::Builder::default()
// .header("wrapper.h")
// .generate()
// wasm-bindgen — Rust ↔ JavaScript
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// Called from JS:
// import { greet } from './pkg/mylib.js'
// greet("World")
// PyO3 — Rust ↔ Python
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> String {
(a + b).to_string()
}
// Neon — Rust ↔ Node.js
// uniffi — cross-language bindings (Kotlin, Swift, Python) Packaging & Distribution
Publish libraries to crates.io. Distribute binaries via cargo install, GitHub releases, or package managers.
# Publish a crate to crates.io
cargo login <token>
cargo publish
# Cargo.toml metadata
[package]
name = "my-crate"
version = "1.0.0"
edition = "2021"
description = "My awesome library"
license = "MIT"
repository = "https://github.com/user/my-crate"
# Install a binary crate
cargo install ripgrep
# Build for release
cargo build --release
# Output: target/release/myapp
# Cross-compilation
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
# Produces fully static binary
# cargo-dist — automated release pipeline
cargo install cargo-dist
cargo dist init
cargo dist build
# Creates GitHub releases with binaries for all platforms
# cargo-binstall — binary installation
cargo binstall ripgrep # Downloads pre-built binary
# Docker (tiny images)
FROM rust:1.78 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=builder /app/target/release/myapp /usr/local/bin/
CMD ["myapp"]
# WASM distribution
wasm-pack build --target web
# Publish to npm: wasm-pack publish