Rust's most distinctive feature and perhaps the most challenging for newcomers is Ownership. It's Rust's approach to memory management, ensuring memory safety without requiring a garbage collector. This allows Rust to achieve high performance while preventing common bugs like dangling pointers, double-frees, and data races at compile time.
At its core, Ownership is a set of rules that the Rust compiler checks at compile time. If these rules are violated, the program won't compile.
Here are the three fundamental rules of Ownership:
1. Each value in Rust has an *owner*.
* An owner is simply the variable that holds the value.
2. There can only be one owner at a time.
* This is crucial for preventing multiple parts of the program from trying to manage the same piece of memory simultaneously, leading to undefined behavior.
3. When the owner goes out of scope, the value will be dropped.
* When a variable (the owner) goes out of scope, Rust automatically calls a `drop` function (if defined for its type) which frees the memory associated with that value. This happens deterministically, without a garbage collector.
Let's break down how these rules manifest in practice:
1. Ownership Transfer (Move)
When you assign a variable to another variable, or pass a variable to a function, Rust performs a "move" for certain types. This means ownership is transferred to the new variable or the function parameter. The original variable is then considered invalid and can no longer be used. This prevents double-free errors, as only one owner is responsible for cleaning up the memory.
Example:
```rust
let s1 = String::from("hello"); // s1 owns "hello"
let s2 = s1; // Ownership moves from s1 to s2. s1 is now invalid.
// println!("{}", s1); // This would cause a compile-time error!
```
However, not all types behave this way. Primitive types like integers, booleans, and fixed-size arrays implement the `Copy` trait. For `Copy` types, assignment creates a duplicate, rather than moving ownership. This is because they have a known size at compile time and are typically stored on the stack, making copying cheap.
2. Borrowing (References)
Sometimes you want to use a value without taking ownership of it. This is where borrowing comes in. You can create a *reference* to a value, which is like giving someone temporary access without transferring ownership. References are immutable by default, meaning you can read the data but not modify it.
```rust
let s = String::from("Rust");
let len = calculate_length(&s); // &s is an immutable reference (a "borrow")
// s is still valid here
```
If you need to modify the data, you can create a *mutable reference* using `&mut`.
```rust
let mut s = String::from("hello");
change_string(&mut s); // &mut s is a mutable reference
// s is modified
```
Rust enforces a strict rule for references, known as the "one mutable XOR many immutable" rule:
* You can have either one mutable reference to a particular piece of data or any number of immutable references to it at any given time, but not both.
* This rule prevents data races at compile time, ensuring that if one part of the code is modifying data, no other part can simultaneously read or modify it, leading to unpredictable behavior.
3. Lifetimes
Lifetimes are a concept related to borrowing that ensure references are always valid. Every reference in Rust has a *lifetime*, which is the scope for which that reference is valid. The Rust compiler uses a "borrow checker" to analyze the lifetimes of all references to ensure that no reference outlives the data it points to. This prevents dangling references (references that point to memory that has already been freed).
Most of the time, the compiler can infer lifetimes, but in more complex scenarios (like function signatures or struct definitions involving references), you might need to explicitly annotate lifetimes.
Benefits of Ownership
* Memory Safety: Guarantees that memory is freed exactly once, preventing double-frees and use-after-free bugs.
* Concurrency Safety: The borrowing rules (especially the "one mutable XOR many immutable" rule) inherently prevent data races, making it safer to write concurrent code.
* Performance: Achieves memory safety without the runtime cost of a garbage collector, leading to predictable performance.
* Predictable Resource Management: `drop` is called deterministically when a value goes out of scope, making it easy to manage other resources like file handles or network connections.
Ownership is a powerful paradigm that requires a shift in thinking, but once mastered, it allows developers to write robust, high-performance, and safe code.
Example Code
fn main() {
// --- 1. Ownership (Move Semantics) ---
// Example of String (heap-allocated type) ownership move
let s1 = String::from("hello"); // s1 owns "hello"
let s2 = s1; // Ownership of "hello" moves from s1 to s2.
// s1 is now invalid and cannot be used.
// The following line would cause a compile-time error: `value borrowed here after move`
// println!("s1: {}", s1);
println!("s2 (after move from s1): {}", s2);
// Example of integer (stack-allocated, Copy type) assignment
let x = 5; // x owns the value 5
let y = x; // 5 is copied to y. x is still valid because i32 implements the Copy trait.
println!("x (after copy to y): {}, y: {}", x, y);
// Ownership with functions
let s3 = String::from("world");
takes_ownership(s3); // s3's ownership moves into the `takes_ownership` function.
// s3 is now invalid.
// The following line would cause a compile-time error: `value borrowed here after move`
// println!("s3 (after function call): {}", s3);
println!("s3 was moved into a function and is no longer valid in main.");
let x2 = 10;
makes_copy(x2); // x2 (i32) is copied into the `makes_copy` function.
// x2 is still valid in main.
println!("x2 (after function call): {}", x2);
// --- 2. Borrowing (References) ---
let s4 = String::from("Rust programming");
// Passing an immutable reference (&String) to a function
let len = calculate_length(&s4); // s4 is borrowed immutably
println!("The length of '{}' is {}. s4 is still valid.", s4, len);
let mut s5 = String::from("mutable string");
// Passing a mutable reference (&mut String) to a function
change_string(&mut s5); // s5 is borrowed mutably
println!("s5 after change_string: '{}'. s5 is still valid.", s5);
// --- 3. Borrowing Rules: One mutable XOR Many immutable ---
let s6 = String::from("exclusive borrow example");
let r1 = &s6; // First immutable borrow
let r2 = &s6; // Second immutable borrow - OK
println!("Immutable borrows: r1 = '{}', r2 = '{}'. s6 is still valid.", r1, r2);
// After r1 and r2 are used, their borrows end.
let mut s7 = String::from("single mutable borrow example");
let mr1 = &mut s7; // First mutable borrow - OK
// The following lines would cause compile-time errors because a mutable borrow is active:
// let mr2 = &mut s7; // ERROR: cannot borrow `s7` as mutable more than once at a time
// let r3 = &s7; // ERROR: cannot borrow `s7` as immutable because it is also borrowed as mutable
println!("Mutable borrow mr1: '{}'. s7 cannot be borrowed again until mr1 is no longer used.", mr1);
// After mr1 is used and its scope (or last usage) ends, another mutable borrow is possible:
// We'll simulate this by putting it in a block or by simply placing it after its last usage.
{ // New scope for another mutable borrow
let mr_new = &mut s7;
mr_new.push_str(" - modified again");
println!("Another mutable borrow (mr_new): '{}'");
} // mr_new goes out of scope here
println!("s7 final state: '{}'.", s7);
}
// Function that takes ownership of a String
fn takes_ownership(some_string: String) { // some_string comes into scope and takes ownership
println!("Function received (takes ownership): {}", some_string);
} // Here, `some_string` goes out of scope. `drop` is called, and the memory is freed.
// Function that takes a Copy type (i32)
fn makes_copy(some_integer: i32) { // some_integer comes into scope (a copy of the value)
println!("Function received (made a copy): {}", some_integer);
} // Here, `some_integer` goes out of scope. Nothing special happens, as it was a copy.
// Function that takes an immutable reference to a String
fn calculate_length(s: &String) -> usize { // s is an immutable reference (borrows s4)
s.len()
} // Here, `s` goes out of scope. The borrow ends. The original String is unaffected.
// Function that takes a mutable reference to a String
fn change_string(some_string: &mut String) { // some_string is a mutable reference (borrows s5 mutably)
some_string.push_str(", world!");
} // Here, `some_string` goes out of scope. The mutable borrow ends. The original String is modified.








What is Ownership in Rust?