Why?
Rust is built around a handful of core rules that make it safe, fast, and concurrency‑friendly.
Mastering these 7 principles is enough to write idiomatic, error‑free Rust and to understand why the compiler sometimes complains.
| # | Principle | What it means (in plain English) |
|---|---|---|
| 1 | Ownership & Single‑Owner | Every value has exactly one owner. When that owner goes out of scope, the value is dropped (freed). |
| 2 | Borrowing & References | You can borrow a value instead of moving it. A borrow is either immutable (&T) or mutable (&mut T). |
| 3 | No Dangling References | A reference can never outlive the data it points to. The compiler enforces this with lifetimes. |
| 4 | No Data Races | Mutability is exclusive: you can have either many immutable borrows or one mutable borrow, never both. |
| 5 | Zero‑Cost Abstractions | High‑level abstractions (iterators, smart pointers) compile to the same machine code as hand‑written loops. |
| 6 | Explicit is Better Than Implicit | Types, lifetimes, and ownership are spelled out in the code. The compiler can reason about everything at compile time. |
| 7 | Safety Is a Spectrum (Safe ↔ Unsafe) | Most code is safe – the compiler guarantees memory safety. If you need to do something lower‑level, you must opt into unsafe, which turns off the guarantees for that block only. |
let s = String::from("hello"); // `s` owns the heap data
// Move ownership to a new variable
let t = s; // `s` is *moved*, cannot be used again
// Drop happens automatically when `t` goes out of scope- Rule: When a value’s owner is dropped, the value is automatically freed.
- Implication: No manual
free()or garbage collection. - Pitfall: Using a value after it has been moved causes a compile‑time error.
let mut s = String::from("hello");
// Immutable borrow – multiple allowed
let r1 = &s;
let r2 = &s;
// Mutable borrow – *exclusive* (no other borrows allowed)
let r3 = &mut s; // ❌ error: cannot borrow `s` as mutable because it is also borrowed as immutable- Rule: You can have either any number of immutable borrows (
&T) or exactly one mutable borrow (&mut T), never both simultaneously. - Why? Prevents data races and ensures memory safety without runtime checks.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}- Rule: Every reference has a lifetime, and the compiler ensures that a reference never outlives the data it points to.
- Common pitfall: Returning a reference that was created inside the function – the compiler will reject it because the lifetime of the local variable ends at the function’s return.
use std::thread;
let mut data = 0;
// Spawn two threads that try to mutate `data`
let t1 = thread::spawn(|| { data += 1; }); // ❌ error: cannot capture `data` mutably- Rule: The borrow checker guarantees that there are no concurrent mutable accesses.
- Result: Rust programs are free from data races without the need for locks or atomics (unless you deliberately use them).
// Iterator abstraction
let sum: i32 = (1..=10).sum(); // compiles to a simple loop- Rule: Rust’s abstractions are monomorphised (generic code is compiled into concrete types) and inlined, so you get the safety of high‑level constructs without a runtime cost.
- Takeaway: Don’t fear abstractions; they’re designed to be as cheap as hand‑written loops.
fn add(a: i32, b: i32) -> i32 { a + b } // types are explicit
let x = 5; // type inferred as `i32` because of context- Rule: The compiler infers types, but you can always annotate them. Explicit annotations improve readability and help the compiler catch subtle bugs early.
- When to annotate: Return types in trait implementations, complex lifetimes, or when the compiler can’t infer a type.
unsafe fn raw_access(ptr: *const i32) -> i32 {
// SAFETY: caller guarantees `ptr` is valid and aligned
*ptr
}-
Rule:
- Safe Rust – the compiler guarantees memory safety.
unsafeblocks – you opt out of those guarantees for a small, well‑documented region. The rest of the code remains safe.
-
Best practice: Keep
unsafeto a minimum and isolate it behind safe abstractions.
| ✅ | Question | Answer |
|---|---|---|
| 1 | Does every value have a single owner? | ✔️ |
| 2 | Can I borrow immutably while mutably borrowing the same data? | ❌ |
| 3 | Will a reference outlive the data it points to? | ❌ (compiler error) |
| 4 | Is there a compile‑time guarantee that my code is free of data races? | ✔️ |
| 5 | Do high‑level abstractions cost me extra runtime? | ❌ (zero‑cost) |
| 6 | Are my types, lifetimes, and ownership explicit enough? | ✅ (or you can annotate) |
| 7 | Do I use unsafe sparingly and only when necessary? |
✅ (or minimize it) |
Rust’s safety guarantees come from a small set of rigid rules:
- Ownership – single owner, automatic drop.
- Borrowing – immutable or exclusive mutable borrow.
- Lifetimes – references never outlive their data.
Once you internalise these three, the rest of Rust (traits, generics, async, etc.) flows naturally. Happy coding!