Rust is a systems programming language that achieves memory safety without a garbage collector through an ownership system enforced at compile time — eliminating use-after-free, null pointer dereferences, buffer overflows, and data races while compiling to native machine code with no runtime overhead.
📑 Rust Reference — All Topics
Move semantics, references, lifetimes, stack vs heap.
Structs, enums, traits, impl blocks, generics.
Result, Option, ? operator, threads, async/await.
A broken elevator, a Mozilla side project, and memory safety without a GC.
rustup installs it; Cargo builds, tests, and manages dependencies.
Immutable by default — mut is an explicit opt-in, not the default.
Fixed-width integers, tuples, and arrays — the width is in the name.
if is an expression, and break can return a value from loop.
One mutable reference OR many immutable — never both, enforced at compile time.
Field-init shorthand, impl blocks, and tuple/unit structs — no classes.
No null — Option is an enum, and match must handle every case.
Monomorphization gives generics zero runtime cost, at the price of binary size.
Vec, String, and HashMap — the growable collections Rust code actually uses.
No exceptions — Result forces explicit handling, and ? makes propagation one character.
Fn, FnMut, FnOnce — the borrow checker needs to know how you use captures.
Lazy by design — map and filter alone produce zero output until consumed.
Box for the heap, Rc for shared ownership, RefCell for runtime-checked mutation.
Annotations describe an existing relationship — they change nothing at runtime.
“Fearless concurrency” — the borrow checker catches most data races before runtime.
std ships the syntax; you add Tokio for an actual runtime.
Private by default at every level — pub is the explicit opt-in.
#[test] and cargo test — tests live right next to the code, no install needed.
vec! and println! aren't functions — the ! means code generated before type checking.
5 specific powers, not a safety-off switch — the borrow checker still applies.
Static dispatch (zero cost) or dyn Trait dynamic dispatch — your choice, per use.
cargo publish uploads forever — yank retires a version, nothing deletes it.
No async runtime, no HTTP client, no JSON — deliberately left to crates.io.
No s[0] by design — UTF-8 characters can span 1-4 bytes.
NLL (2018+) made the checker smarter, not weaker — borrows now end at last use.
Zero-cost abstractions: iterators compile to the identical assembly as a manual loop.
Rust achieves what was previously thought impossible: memory safety without a garbage collector — the compiler enforces rules about who owns data and when it is freed, catching entire classes of bugs that crash C and C++ programs before your code ever runs.
What Rust is
Rust is a systems programming language created by Graydon Hoare at Mozilla Research, with version 1.0 released in May 2015. Rust's central achievement is memory safety without garbage collection — it eliminates use-after-free, null pointer dereferences, buffer overflows, and data races at compile time through its ownership system, without any runtime overhead. Rust compiles to native machine code and matches C and C++ in performance, while providing the safety guarantees that those languages cannot.
Rust has been the most loved/admired programming language in the Stack Overflow Developer Survey every year from 2016 to 2024. It is used in the Linux kernel (since 2022), Android, the Windows NT kernel, Firefox, Cloudflare's infrastructure, AWS, Discord, Figma, and hundreds of production systems.
fn main() {
// Variables are immutable by default
let name = "Priya";
let mut count = 0; // mut = mutable
println!("Hello, {}!", name);
// Loop with mutation
while count < 5 {
count += 1;
}
println!("Count: {}", count);
// Ownership: each value has exactly one owner
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED into s2 — s1 no longer valid
// println!("{}", s1); // COMPILE ERROR: s1 was moved
// Clone to make a deep copy
let s3 = s2.clone();
println!("{} and {}", s2, s3);
}Ownership — the central concept
Rust's ownership system enforces three rules at compile time:
- Each value in Rust has exactly one owner.
- When the owner goes out of scope, the value is dropped (freed).
- There can only be one owner at a time — assignment moves ownership.
These rules let the compiler insert free calls automatically at the right points — no manual memory management, no garbage collector, no runtime overhead. Memory is always freed exactly once, exactly when the owner goes out of scope.
fn main() {
// Stack-allocated — Copy trait: integers, bools, chars
let x = 5;
let y = x; // x is COPIED — both x and y are valid
println!("x={}, y={}", x, y);
// Heap-allocated — no Copy: String, Vec, Box
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED — only s2 is valid now
// println!("{}", s1); // ERROR: value moved
// Passing to function also moves
let s = String::from("world");
takes_ownership(s);
// println!("{}", s); // ERROR: s was moved into the function
// Return ownership back
let s3 = gives_ownership();
println!("{}", s3);
}
fn takes_ownership(s: String) {
println!("{}", s);
} // s dropped here — memory freed
fn gives_ownership() -> String {
String::from("hello")
} // returned String moved to caller — not dropped hereBorrowing and references
Moving ownership to every function is impractical. Borrowing lets you pass a reference to a value without transferring ownership. The rules: you can have either one mutable reference OR any number of immutable references — but never both at the same time. This prevents data races at compile time.
fn main() {
let s = String::from("hello");
// Immutable borrow — s not moved, caller retains ownership
let len = calculate_length(&s);
println!("'{}' has {} chars", s, len);
// Multiple immutable borrows are OK
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// Mutable borrow — only ONE at a time
let mut s = String::from("hello");
let r = &mut s;
r.push_str(", world");
println!("{}", r);
// Cannot have another reference to s while r is alive
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s not dropped — we only borrowed itTypes: structs, enums, and Result/Option
Rust's enum is much more powerful than enums in C or Java — enum variants can carry data. The two most important enums in Rust: Option<T> (a value that may or may not be present) and Result<T, E> (an operation that may succeed or fail). Both eliminate null pointer exceptions and unhandled errors by making the compiler force you to handle every case.
// Struct
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self { Point { x, y } }
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// Enum with data (algebraic data type)
enum Shape {
Circle(f64), // radius
Rectangle(f64, f64), // width, height
Triangle(f64, f64, f64), // sides
}
fn area(shape: &Shape) -> f64 {
match shape {
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() // Heron's formula
}
}
}
// Option: Some(value) or None
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
// Result: Ok(value) or Err(error)
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>()
}Lifetimes: references that outlive their data
The borrow checker prevents dangling references — references to data that has already been freed. When a function takes and returns references, the compiler needs to know how the reference lifetimes relate. Lifetime annotations ('a, 'b) make these relationships explicit. The compiler often infers lifetimes (lifetime elision), but complex cases require explicit annotations.
// The return reference must live at least as long as both inputs
// 'a says: the output lifetime is tied to the shorter of x and y
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 Important<'a> {
content: &'a str, // content must outlive Important
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("{}", result); // OK — s2 still alive here
}
// println!("{}", result); // ERROR — s2 dropped; result would dangle
}Traits: Rust's interface system
A trait defines shared behaviour — like an interface in Java or a typeclass in Haskell. Types implement traits. Trait bounds constrain generics (T: Display + Clone). Rust's standard library is built on traits: Iterator, Display, Clone, Copy, Send, Sync.
use std::fmt;
trait Animal {
fn name(&self) -> &str;
fn speak(&self) -> String;
// Default implementation
fn description(&self) -> String {
format!("{} says: {}", self.name(), self.speak())
}
}
struct Dog { name: String }
struct Cat { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
fn speak(&self) -> String { String::from("Woof!") }
}
impl Animal for Cat {
fn name(&self) -> &str { &self.name }
fn speak(&self) -> String { String::from("Meow!") }
}
// Trait bound: T must implement Animal
fn print_animal<T: Animal>(animal: &T) {
println!("{}", animal.description());
}
// Dynamic dispatch: dyn Animal — runtime polymorphism
fn make_sound(animals: &[Box<dyn Animal>]) {
for a in animals { println!("{}", a.speak()); }
}Fearless concurrency
Rust's ownership and type system prevent data races at compile time. The Send trait marks types safe to transfer between threads. The Sync trait marks types safe to share between threads via references. The compiler enforces these — if a type is not Send, you cannot send it to another thread. This is "fearless concurrency": concurrency bugs become compile errors.
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
// Arc = Atomically Reference Counted (thread-safe Rc)
// Mutex = mutual exclusion for shared mutation
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles { handle.join().unwrap(); }
println!("Result: {}", *counter.lock().unwrap()); // 10
// Channels: message passing (like Go goroutines)
let (tx, rx) = std::sync::mpsc::channel();
thread::spawn(move || { tx.send(42).unwrap(); });
println!("Received: {}", rx.recv().unwrap());
}Cargo: Rust's build system and package manager
Cargo is Rust's official build tool, package manager, and project scaffolder. cargo new project_name creates a new project. cargo build compiles. cargo run compiles and runs. cargo test runs tests. cargo doc generates documentation. Dependencies are declared in Cargo.toml; exact versions are locked in Cargo.lock. The package registry is crates.io — over 150,000 crates published.
.await it or spawn it on an executor for it to run.The ownership system: formal foundations
Rust's ownership system is formalized in RustBelt (Jung et al., 2017), a mechanized semantic model of the Rust type system built in Iris/Coq. RustBelt proved that Rust's core type system is sound — if the compiler accepts a program, it is memory-safe and data-race-free (assuming no unsafe blocks). The key insight: Rust's type system encodes a linear type discipline where values are consumed exactly once, combined with a region-based memory model where borrows are valid for exactly their declared lifetime. The proof covers the entire Rust standard library's use of unsafe, verifying that all unsafe abstractions maintain the safety invariants expected by safe code.
unsafe Rust and the safety boundary
Rust has an unsafe keyword that unlocks five additional capabilities: dereferencing raw pointers, calling unsafe functions, accessing mutable static variables, implementing unsafe traits, and accessing union fields. The programmer asserts these operations are correct; the compiler stops verifying them. unsafe does not disable the borrow checker or type system generally — it only permits those five operations within the unsafe block. The fundamental contract: safe Rust code may call into unsafe abstractions; those abstractions are responsible for maintaining the invariants that make the rest of the code safe. The Rust standard library itself uses unsafe internally for performance-critical operations (e.g. Vec::push) while presenting a safe public API.
Rust in the Linux kernel and Windows
Rust was accepted into the Linux kernel in version 6.1 (December 2022) as a second implementation language alongside C (Linus Torvalds merged the initial support; Miguel Ojeda leads the effort). Rust code in the kernel must be no_std (no standard library) and operate within the kernel's custom allocator. Microsoft has been rewriting portions of the Windows NT kernel in Rust since 2023, starting with the kernel-mode driver infrastructure. Both efforts cite the same motivation: eliminating the memory safety vulnerabilities (use-after-free, buffer overflows) that account for the majority of CVEs in both kernels.
Klabnik, S. & Nichols, C. (2022). The Rust Programming Language (2nd ed.). No Starch Press. doc.rust-lang.org/book/. Jung, R. et al. (2017). "RustBelt: Securing the Foundations of the Rust Programming Language." POPL 2018. dl.acm.org/doi/10.1145/3158154. Rust Reference: doc.rust-lang.org/reference/. Rust Nomicon (unsafe Rust): doc.rust-lang.org/nomicon/.