Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Understanding Ownership in Rust

3 min read .
Understanding Ownership in Rust

Ownership is one of Rust’s defining features. It lets Rust manage memory safely without a garbage collector by enforcing rules about who owns a value, when ownership moves, and when values are dropped.

Understanding ownership is essential because it affects variable scope, function calls, references, borrowing, and many compiler errors you will encounter while learning Rust.

What Is Ownership?

Ownership is Rust’s model for managing resources. A value has an owner, and Rust can determine at compile time when that value is no longer needed. This design helps prevent problems such as use-after-free bugs, double frees, dangling references, and data races.

The Three Ownership Rules

A useful summary is:

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

Rule 1: Every Value Has an Owner

fn main() {
    let s = String::from("Hello, Rust!"); // s owns the String
    println!("{}", s); // s is still valid here
} // s leaves scope and the String is dropped

The variable s owns the allocated String. When s leaves scope, Rust automatically releases the resource.

Rule 2: Ownership Can Move

For types such as String, assignment usually moves ownership instead of duplicating the heap allocation:

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

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

After the move, s2 owns the string and s1 can no longer be used.

Types that implement the Copy trait, such as many primitive numeric types, behave differently: their values are copied instead of moved.

Rule 3: Values Are Dropped When Their Owner Leaves Scope

fn main() {
    let s = String::from("Goodbye, Rust!");
    // s is dropped automatically at the end of this scope
}

Rust calls the value’s Drop implementation when appropriate, releasing resources deterministically.

References and Borrowing

A reference lets code access a value without taking ownership of it. This is called borrowing.

Immutable References

Multiple immutable references can read the same value:

fn main() {
    let s = String::from("Rust");
    let len = calculate_length(&s);
    println!("The length of '{}' is {}.", s, len);
}

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

The function borrows s, so ownership remains in main.

Mutable References

A mutable reference lets the borrower modify a value:

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

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

Rust restricts overlapping mutable access so two parts of a program cannot mutate the same data unsafely at the same time.

A simplified view of the borrowing rules is:

  1. You may have multiple immutable references while there is no active mutable reference.
  2. You may have one mutable reference to a value for a given overlapping lifetime.
  3. Mutable and immutable borrows cannot overlap when that would create conflicting access.

Rust’s non-lexical lifetimes can end a borrow before the end of the surrounding block once the reference is no longer used.

Slices Borrow Part of a Value

Slices reference a contiguous part of a collection without taking ownership:

fn main() {
    let s = String::from("Hello, world!");
    let hello = &s[0..5];
    let world = &s[7..12];

    println!("{} {}", hello, world);
}

For text-processing APIs, &str is often more flexible than &String because it can borrow from both owned strings and string literals.

Practical Guidelines

  1. Pass references when a function only needs to inspect or modify a value temporarily.
  2. Move ownership when the receiving code should become responsible for the value.
  3. Use slices when a function only needs part of a string, array, or vector.
  4. Read borrow-checker diagnostics carefully; they usually explain which borrow or move overlaps another use.
  5. Avoid cloning only to silence an ownership error unless copying the data is genuinely the intended behavior.

Conclusion

Ownership is the foundation of Rust’s memory-safety model. Once moves, borrowing, references, and scopes become familiar, many Rust APIs become easier to understand and the borrow checker becomes a design aid rather than an obstacle.

Related Posts

chevron-up