Skip to content

Instantly share code, notes, and snippets.

@erhangundogan
Created September 15, 2025 07:12
Show Gist options
  • Select an option

  • Save erhangundogan/6c1c216be8f21079f524fbd3c4eb3480 to your computer and use it in GitHub Desktop.

Select an option

Save erhangundogan/6c1c216be8f21079f524fbd3c4eb3480 to your computer and use it in GitHub Desktop.
Rust Fundamentals

The Rust “Rule‑Book” – 7 Fundamental Principles Everyone Should Know

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.

1️⃣ Ownership – “One owner, one drop”

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.

2️⃣ Borrowing – “Read or Write, but not both”

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.

3️⃣ Lifetimes – “References can’t outlive their data”

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.

4️⃣ No Data Races – “Exclusive mutation is a compile‑time guarantee”

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).

5️⃣ Zero‑Cost Abstractions – “Write high‑level, run low‑level”

// 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.

6️⃣ Explicit is Better Than Implicit – “Everything spelled out”

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.

7️⃣ Safety Spectrum – “Choose your level of safety”

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.
    • unsafe blocks – you opt out of those guarantees for a small, well‑documented region. The rest of the code remains safe.
  • Best practice: Keep unsafe to a minimum and isolate it behind safe abstractions.


Quick Checklist for New Rustaceans

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)

Bottom Line

Rust’s safety guarantees come from a small set of rigid rules:

  1. Ownership – single owner, automatic drop.
  2. Borrowing – immutable or exclusive mutable borrow.
  3. Lifetimes – references never outlive their data.

Once you internalise these three, the rest of Rust (traits, generics, async, etc.) flows naturally. Happy coding!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment