💡 Introduction — Why Rust?
Rust delivers memory safety without a GC, predictable performance, and strong concurrency guarantees. It's used for systems programming, networking, game engines, CLI tools, WebAssembly, and algorithmic code where low-level control matters.
This guide focuses on concepts (ownership, borrowing, lifetimes), modern Rust idioms, async programming, and DSA-ready patterns and examples.
🛠️ Toolchain: rustup, cargo & crates.io
- rustup — toolchain installer & manager (stable/nightly/channels).
- cargo — build, test, bench, run, manage dependencies; your primary workflow tool.
- crates.io — package registry; use trusted crates (tokio, anyhow, serde, rayon).
- rustfmt & clippy — formatting and lints; use them in CI.
cargo new, cargo build --release, cargo test, cargo bench are core commands.
🔐 Ownership, Borrowing & Lifetimes
Ownership is Rust's core: every value has one owner. Move semantics, borrowing (&) and mutable borrowing (&mut) enforce safety at compile time.
```
fn main() {
let s = String::from("hello"); // owner s
let s2 = s; // move: s is invalid after this
// println!("{}", s); // compile error
let x = 5;
let y = x; // Copy trait: x still usable
} Lifetimes connect borrows to data — Rust infers many lifetimes; explicit annotations appear when needed for complex references.
🔤 Types, Mutability & Pattern Matching
Rust is statically typed with powerful pattern matching and algebraic data types (enums).
```
let mut x: i32 = 10; // mutable
let y = &x; // immutable borrow
match some_option {
Some(v) => println!("{}", v),
None => println!("none")
}
enum Tree { Leaf(i32), Node(Box, Box) } Pattern matching and enums make control flow expressive and safe (no null). Prefer Option over nullable references.
🧠 Memory Safety & Zero-Cost Abstractions
Rust enforces memory safety at compile time; abstractions (iterators, closures, traits) compile down without runtime cost.
- Stack-allocated values vs heap via
Box,Vec,String. - No GC — deterministic drop via
Droptrait. - Borrow checker prevents data races in safe code (Send + Sync traits for concurrency).
🛡️ Error Handling: Option & Result
Rust encourages explicit error handling with Option and Result.
```
use std::fs::File;
use std::io::{self, Read};
fn read_file(path: &str) -> Result {
let mut s = String::new();
File::open(path)?.read_to_string(&mut s)?;
Ok(s)
} Use the ? operator for propagation and crates like anyhow or thiserror for ergonomic error handling in apps.
🔧 Traits, Generics & Monomorphization
Traits are type classes (interfaces). Generics are monomorphized at compile time, producing optimized code for each type.
```
trait Printable { fn print(&self); }
impl Printable for i32 {
fn print(&self) { println!("num: {}", self); }
}
fn show(x: T) { x.print(); } Use trait bounds and where-clauses for complex generic constraints. Prefer iterator adapters and zero-cost patterns.
⚡ Concurrency: Threads, async & Tokio
Rust provides both native threads and async. tokio is the most popular async runtime; rayon for data parallelism.
```
// simple thread example
use std::thread;
let handle = thread::spawn(|| { println!("hello from thread"); });
handle.join().unwrap();
// async with tokio
// #[tokio::main]
// async fn main() {
// let res = reqwest::get("[https://example.com").await.unwrap(](https://example.com%22%29.await.unwrap%28));
// println!("{}", res.status());
// } Safe concurrency: Send + Sync markers ensure types are safe to transfer across threads. Use channels (std::sync::mpsc or tokio::sync) for communication.
📚 Collections: Vec, HashMap, BTreeMap
Rust standard collections are in std::collections. Use Vec for dynamic arrays; HashMap and BTreeMap for maps.
```
use std::collections::{HashMap, BTreeMap};
let mut v: Vec = Vec::with_capacity(100);
v.push(1);
let mut h = HashMap::new();
h.insert("a", 1);
let mut b = BTreeMap::new();
b.insert(1, "one"); Understand ownership when inserting/borrowing into collections. Prefer iterators and .iter().map().collect() for transformations.
🎯 DSA Techniques in Rust
Rust is well-suited for algorithmic coding — strong typing, low-overhead abstractions, and efficient collections.
- Preallocate Vec capacity:
Vec::with_capacity(n). - Use slices
&[T]for borrowing arrays without copying. - Use
binary_search,sort_unstablefor performance;sort_unstableis often faster when stability not required. - Use
VecDequeor index-based deque patterns for sliding-window problems. - For priority queues, use
std::collections::BinaryHeap(max-heap by default). - Avoid unnecessary cloning — prefer references and lifetime-driven borrows.
🔗 FFI & Interop (C, WASM)
Rust has excellent FFI. Use #[no_mangle] and extern "C" for C interop, and wasm-bindgen for WebAssembly.
```
// expose to C
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
// wasm example uses wasm-bindgen (not shown here) When using FFI, be explicit about ownership and memory boundaries to avoid UB.
🧩 Expanded Examples (Rust)
1. BFS (Adjacency List)
``` use std::collections::VecDeque; fn bfs(adj: &Vec```>, start: usize) -> Vec { let n = adj.len(); let mut vis = vec![false; n]; let mut q = VecDeque::new(); let mut res = Vec::new(); vis[start] = true; q.push_back(start); while let Some(u) = q.pop_front() { res.push(u); for &v in &adj[u] { if !vis[v] { vis[v] = true; q.push_back(v); } } } res }
2. Union-Find (DSU)
```
struct DSU { parent: Vec, rank: Vec }
impl DSU {
fn new(n: usize) -> Self {
DSU { parent: (0..n).collect(), rank: vec![0; n] }
}
fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x { self.parent[x] = self.find(self.parent[x]); }
self.parent[x]
}
fn union(&mut self, a: usize, b: usize) -> bool {
let mut x = self.find(a); let mut y = self.find(b);
if x == y { return false; }
if self.rank[x] < self.rank[y] { std::mem::swap(&mut x, &mut y); }
self.parent[y] = x;
if self.rank[x] == self.rank[y] { self.rank[x] += 1; }
true
}
}
```
3. Min-Heap (BinaryHeap)
``` use std::collections::BinaryHeap; use std::cmp::Reverse; fn kth_smallest(nums: Vec```, k: usize) -> i32 { let mut heap = BinaryHeap::new(); // max-heap for &x in &nums { heap.push(x); if heap.len() > k { heap.pop(); } } *heap.peek().unwrap() } // for min-heap, use BinaryHeap >
4. Sliding Window Maximum (deque of indices)
``` fn max_sliding_window(nums: &[i32], k: usize) -> Vec```{ use std::collections::VecDeque; if k == 0 { return vec![]; } let mut dq: VecDeque = VecDeque::new(); let mut res = Vec::with_capacity(nums.len().saturating_sub(k) + 1); for i in 0..nums.len() { if let Some(&front) = dq.front() { if front + k == i { dq.pop_front(); } } while let Some(&back) = dq.back() { if nums[back] < nums[i] { dq.pop_back(); } else { break; } } dq.push_back(i); if i + 1 >= k { res.push(nums[*dq.front().unwrap()]); } } res }
These examples favor idiomatic Rust: minimal clones, borrow usage, and preallocation where appropriate.
🚀 Performance & Profiling
- Build release binaries:
cargo build --release(enable LTO if needed). - Use
cargo benchandcriterionfor reliable benchmarks. - Profile with
perf,flamegraph, ortokio-consolefor async workloads. - Avoid needless allocation: prefer slices, references, & iterators; use
Vec::with_capacity.
✅ Best Practices & Common Pitfalls
- Prefer references and borrowing over cloning; clone explicitly.
- Minimize
unsafe; when necessary, wrap and document invariants thoroughly and test aggressively. - Use
cargo fmtandclippyto keep code idiomatic and catch pitfalls. - Be mindful of iterator adapters that may allocate; inspect generated code for hot paths.
- Understand lifetime errors: they guide correct ownership, not an obstacle — refactor to simpler ownership models when stuck.
🏁 Final Summary — Rust Proficiency Checklist
Master these to write safe, high-performance Rust and to apply it in DSA/production:
- Ownership, borrowing, lifetimes — the heart of Rust safety
- Tooling with cargo, rustup, crates.io; format and lint with rustfmt/clippy
- Pattern matching, enums, and idiomatic error handling with Option/Result
- Concurrency with threads, async (tokio), and data-parallelism (rayon)
- DSA patterns using Vec, BinaryHeap, VecDeque, HashMap, and BTreeMap
- Profiling, benchmarks, and avoiding needless allocations
Next: convert your DSA templates to Rust, write small systems-level utilities, and experiment with async networking using tokio + hyper.