JSTAcademy
0 XP
Dashboard
Crash Courses
Ownership, Borrowing & Lifetimes
45 min
Basic+160 XP
Crash Courses · Basic

Ownership, Borrowing & Lifetimes

Rust's core memory model — no GC, no dangling pointers
45 min read+160 XP on completionCert: Rust Crash Course
Tap any word in the text below to start reading from there.

Ownership, Borrowing & Lifetimes

The Three Ownership Rules

  1. Each value has exactly one owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped.

Move Semantics

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 is MOVED into s2 — s1 is no longer valid

    // println!("{}", s1); // compile error: value moved
    println!("{}", s2); // ok
}

String is heap-allocated and does not implement Copy — assignment moves ownership.

Copy Types

Primitive types implement Copy — assignment duplicates the value:

let x = 5;
let y = x; // x is COPIED, not moved
println!("{} {}", x, y); // both valid: 5 5

Copy types: integers, floats, bool, char, tuples of Copy types.

Cloning

Explicitly deep-copy a non-Copy type:

let s1 = String::from("hello");
let s2 = s1.clone(); // explicit heap allocation
println!("{} {}", s1, s2); // both valid

Borrowing (References)

Pass a reference to avoid moving ownership:

fn length(s: &String) -> usize { // borrows s
    s.len()
}

fn main() {
    let s = String::from("hello");
    let len = length(&s); // pass reference
    println!("{} has {} chars", s, len); // s still valid
}

Mutable References

fn append_world(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{}", s); // hello, world
}

Rule: you can have EITHER any number of &T OR exactly one &mut T — never both simultaneously. This prevents data races at compile time.

Lifetimes

Lifetimes annotate how long references live. The compiler infers most of them:

// 'a says: the returned reference lives as long as the shorter-lived input
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xy");
        result = longest(s1.as_str(), s2.as_str());
        println!("{}", result); // ok — both alive here
    }
}

You rarely write lifetime annotations in practice — the compiler's lifetime elision rules handle most cases.

0%