Ownership and Borrowing
Ownership
Rust's ownership system ensures memory safety without needing a garbage collector. It is a set of rules that the compiler enforces at compile time:
/// This example demonstrates the concept of ownership and move semantics in /// Rust. fn main() { // Strings have move semantics. // `s1` owns the string "hello". let s1 = String::from("hello"); // `s1` is MOVED into `s2`. // This is NOT a shallow copy. // `s2` now owns the string "hello". let s2 = s1; println!("{}, world!", s2); // Rust has invalidated `s1` because the String has been moved to `s2`. // ERROR: println!("{}, world!", s1); }
When the owner goes out of scope, the value is dropped.
fn main() { // `s` is not valid here, it’s not yet declared. { // `s` is valid from this point forward. let s = String::from("hello"); println!("{}", s); } // this scope is now over, and `s` is no longer valid. // Rust calls `drop`. // ERROR println!("{}", s); // `s` is not valid here, it’s no longer in // scope. }
Rust will never automatically create deep copies of your data. Use the std::clone::Clone
⮳ trait to explicitly create a deep copy.
/// This example demonstrates the use of the `clone` method to create a deep /// copy of a `String`. fn main() { // Create a String on the heap. let s1 = String::from("hello"); // Clone the String, creating a new String with the same content on the // heap. let s2 = s1.clone(); // `clone` deeply copies the heap data of the `String`, // not just the stack data. println!("{s2}"); }
The Copy
trait is a marker trait, meaning it doesn't have any methods. It's used to indicate that a type can be copied by simply copying its bits.
If a type implements the std::marker::Copy
⮳ trait (which is the case for stack-only, fixed-size values, like integers, floats, and tuples thereof), variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable. Types that implement Copy
are implicitly Clone
.
/// Integers implement the `Copy` trait, so they are copied instead of moved. fn main() { let x = 5; // x is an integer. let y = x; // y is a copy of x. // Both x and y are valid here. println!("x = {}, y = {}", x, y); }
Borrowing
Passing a variable to a function will move or copy, just as assignment does. To avoid transferring ownership, you can "borrow" the value:
//! Demonstrates the concept of borrowing in Rust. fn main() { let s1 = String::from("hello"); let _len = calculate_length(&s1); // `&s1` passes an immutable reference to `s1` fn calculate_length(s: &str) -> usize { s.len() } // `s` goes out of scope here. Because the function does not have // ownership of what it refers to, `s1` is not dropped. println!("{s1}"); }
Mutable References
Mutable references allow you to modify the borrowed value. However, there are strict rules to prevent data races:
If you have a mutable reference to a value, you can have no other simultaneous references to that value! References function like a read/write lock.
/// This function takes a mutable reference to a String and appends ", world" to /// it. fn change(some_string: &mut String) { some_string.push_str(", world"); // Modifies the string in place. println!("{some_string}"); } fn main() { let mut s = String::from("hello"); // Note the `mut` keyword. change(&mut s); }
Related Topics
- Lifetimes.
- Rust Patterns.