Rust Ownership & Lifetimes

What is Ownership?

Rust does not have garbage collection. Instead, it has ownership rules to manage memory automatically.

Rule #1: Every Value Has One Owner

fn main() {
    let banana = String::from("banana"); // banana owns this String
    let monkey = banana; // Ownership MOVED to monkey!
    // println!("{}", banana); // ERROR! banana lost ownership!
    println!("{}", monkey); // Works!
}

When banana gives ownership to monkey, it loses access to the value.

Rule #2: When the Owner Dies, the Value is Dropped

fn main() {
    {
        let food = String::from("apple"); // food is created
        println!("Monkey eats: {}", food);
    } // food goes out of scope, and is DROPPED
    // println!("{}", food); // ERROR! food is gone!
}

When food goes out of scope, Rust automatically cleans it up.

Borrowing

Rust allows borrowing instead of moving ownership.

Rule #3: You Can Borrow Without Taking Ownership

fn eat(banana: &String) { // Borrowing (not moving)
    println!("Monkey eats: {}", banana);
}

fn main() {
    let banana = String::from("banana");
    eat(&banana); // Works! banana is still owned by main
    println!("Banana is still here: {}", banana);
}

Borrowing (&) allows you to use a value without owning it.

Rule #4: No Mutability Conflicts

You can:

  1. Have multiple immutable references (readonly)
fn main() {
    let mut banana = String::from("banana");
    let monkey1 = &banana; // Immutable borrow
    let monkey2 = &banana; // Another immutable borrow
    println!("Monkeys see: {} and {}", monkey1, monkey2); // Works!

    // let monkey3 = &mut banana; // ERROR! Can't mutate while others read!
}
  1. OR only one mutable reference (write access)
fn main() {
    let mut banana = String::from("banana");
    let monkey = &mut banana; // Allowed, only ONE mutable borrow
    monkey.push_str(" banana"); // Modify banana!
    println!("Monkey added more bananas: {}", monkey);
}

Lifetimes

Rust checks lifetimes to make sure values don’t disappear while being borrowed.

Rule #5: Lifetimes Define How Long a Borrow is Valid

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() { s1 } else { s2 }
}

fn main() {
    let banana = String::from("banana banana banana");
    let apple = String::from("apple");
    let best = longest(&banana, &apple);
    println!("Best fruit: {}", best);
}

Here, longest<’a> means both inputs and output share the same lifetime.

What Happens When Lifetimes Are Wrong?

fn bad() -> &String { // ERROR! No lifetime!
    let banana = String::from("banana");
    &banana // banana is dropped here!
}

The value disappears before it can be returned.

Summary