Skip to content

Rust Basics - Ownership, Borrowing & Lifetimes

Rust Basics - Ownership, Borrowing & Lifetimes

These three concepts form the foundation of Rust's memory safety guarantees. Understanding them is key to writing Rust code.

Ownership

Ownership is Rust's most unique feature. It enables memory safety without a garbage collector.

The Rules

  1. Each value has one owner
  2. Only one owner at a time
  3. Value is dropped when owner goes out of scope
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1's value moves to s2
    
    // println!("{}", s1);  // ERROR! s1 is no longer valid
    println!("{}", s2);     // OK
}

Move Semantics

fn take_ownership(s: String) {
    println!("{}", s);
} // s is dropped here

fn main() { let s = String::from("hello"); take_ownership(s); // s is no longer valid here }

Copy Types

Primitive types implement Copy, so they're copied instead of moved:

let x = 5;
let y = x;  // x is copied, not moved

println!("{}", x); // OK! println!("{}", y);

Borrowing

Instead of transferring ownership, we can borrow values:

Immutable References

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn main() { let s = String::from("hello"); let len = calculate_length(&s); println!("'{}' length is {}", s, len); // s is still valid! }

Mutable References

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

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

The Rules of References

  1. One mutable reference OR any number of immutable references
  2. References must always be valid
let mut s = String::from("hello");

let r1 = &s; // OK let r2 = &s; // OK - multiple immutable // let r3 = &mut s; // ERROR! Cannot borrow as mutable

println!("{} {}", r1, r2);

let r3 = &mut s; // OK - r1 and r2 are no longer used

Lifetimes

Lifetimes ensure references are valid as long as we need them.

Lifetime Annotation Syntax

&i32        // a reference
&'a i32     // a reference with explicit lifetime
&'a mut i32 // a mutable reference with explicit lifetime

Function with Lifetimes

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"); { let s2 = String::from("x"); let result = longest(&s1, &s2); println!("{}", result); } }

Struct with Lifetimes

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap(); let i = ImportantExcerpt { part: first_sentence, }; }

Lifetime Elision

Rust can often infer lifetimes:

// These are equivalent:
fn first_word(s: &str) -> &str { }
fn first_word<'a>(s: &'a str) -> &'a str { }

Common Patterns

Returning Owned Data

When in doubt, return owned data:

fn get_string() -> String {
    String::from("owned")
}

The Clone Trait

Explicitly clone when needed:

let s1 = String::from("hello");
let s2 = s1.clone();  // Deep copy

Summary

| Concept | Purpose | Syntax | |---------|---------|--------| | Ownership | Who owns the data | let s = String::from("hi") | | Borrowing | Temporary access | &s, &mut s | | Lifetimes | Ensure references valid | <'a> |

Master these, and you'll write safe, efficient Rust code!