TUTORIAL

Understanding Ownership and Borrowing in Rust

Master Rust's most distinctive feature — ownership — and learn how it enables memory safety without a garbage collector.


Table of Contents

Understanding Ownership and Borrowing in Rust

Rust's ownership system is what makes it unique among systems programming languages. It enforces memory safety at compile time, eliminating entire classes of bugs like use-after-free, double-free, and data races — without a garbage collector.

What Is Ownership?

Every value in Rust has a single owner — a variable that holds that value. When the owner goes out of scope, Rust automatically frees the memory.

Rust
fn main() {
    let s = String::from("hello"); // s owns the String
    println!("{}", s);
} // s drops here — memory freed automatically
Rust's ownership rules are enforced entirely at **compile time**. There is zero runtime overhead for these checks.

The Three Rules of Ownership

1. Each value in Rust has a variable called its *owner*. 2. There can only be one owner at a time. 3. When the owner goes out of scope, the value will be dropped.

Move Semantics

When you assign a heap-allocated value to another variable, ownership moves. The original variable becomes invalid:

Rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // ownership moves to s2

    // println!("{}", s1); // ❌ compile error: s1 no longer valid
    println!("{}", s2);    // ✅ works fine
}

Stack-allocated types (like integers, booleans, f64) implement the Copy trait and are copied instead of moved:

Rust
fn main() {
    let x = 5;
    let y = x; // x is copied, not moved
    println!("x={}, y={}", x, y); // ✅ both valid
}

Borrowing

Instead of transferring ownership, you can borrow a value using references:

Rust
fn calculate_length(s: &String) -> usize {
    s.len()
} // s goes out of scope but doesn't drop the String (it doesn't own it)

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // borrow s1
    println!("'{}' has {} characters", s1, len); // ✅ s1 still valid
}

Mutable References

To modify a borrowed value, use &mut:

Rust
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"
}
You can have **either** one mutable reference **or** any number of immutable references to a value in a given scope — never both simultaneously. This prevents data races at compile time.

The Borrow Checker

The borrow checker enforces borrowing rules:

Rust
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;     // immutable borrow
    let r2 = &s;     // another immutable borrow — OK
    // let r3 = &mut s; // ❌ cannot borrow mutably while immutably borrowed

    println!("{} and {}", r1, r2);
    // r1 and r2 no longer used after this point

    let r3 = &mut s; // ✅ OK now — previous borrows ended
    r3.push_str("!");
    println!("{}", r3);
}

Lifetimes

Lifetimes are Rust's way of ensuring references don't outlive the data they point to:

Rust
// 'a is a lifetime annotation: output lives at least as long as 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("xyz");
        result = longest(s1.as_str(), s2.as_str());
        println!("Longest: {}", result); // ✅ both in scope
    }
}

Summary

Concept What it means
Ownership Each value has one owner; dropped when owner leaves scope
Move Ownership transfers; original variable invalidated
Copy Stack types are copied, not moved
Borrow (&T) Immutable reference; multiple allowed
Borrow (&mut T) Mutable reference; only one at a time
Lifetime ('a) Ensures references are always valid

Rust's ownership model takes getting used to, but it pays dividends: you get memory safety, no garbage collector pauses, and thread safety — all guaranteed at compile time.


Was this article helpful?

w

webencher Editorial

Software engineers and technical writers with 10+ years of combined experience in algorithms, systems design, and web development. Every article is reviewed for accuracy, depth, and practical applicability.

More by this author →