What is Ownership?
Ownership is Rust’s most unique feature, enabling Rust to guarantee memory safety without needing a garbage collector. Understanding ownership is crucial for mastering Rust.
Ownership Rules
Rust’s ownership follows three simple rules:
- Every value in Rust has an owner (owner)
- At any given time, there can only be one owner for a value
- When the owner goes out of scope, the value is automatically dropped
fn main() {
let s1 = String::from("hello"); // s1 owns this string
let s2 = s1; // ownership moves to s2
// println!("{}", s1); // Compile error! s1 is no longer valid
println!("{}", s2); // Works fine
}rustMove Semantics vs Copy Semantics
For simple types on the stack (like integers, booleans), Rust automatically copies:
let x = 5;
let y = x; // Copy, both x and y are valid
println!("x = {}, y = {}", x, y); // Works finerustFor complex types on the heap (like String), the default is move semantics:
let s1 = String::from("hello");
let s2 = s1.clone(); // Explicit deep copy
println!("s1 = {}, s2 = {}", s1, s2); // Works finerustBorrowing and References
If we don’t want to transfer ownership, we can use references (borrowing):
Immutable References
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // Borrow s
println!("The length of '{}' is {}", s, len); // s is still valid
}rustMutable References
fn add_world(s: &mut String) {
s.push_str(", world!");
}
fn main() {
let mut s = String::from("hello");
add_world(&mut s);
println!("{}", s); // Output: hello, world!
}rustBorrowing Rules
Rust’s borrow checker enforces the following rules:
- At any given time, you can have either one mutable reference or any number of immutable references
- References must always be valid
let mut s = String::from("hello");
let r1 = &s; // Immutable reference - OK
let r2 = &s; // Immutable reference - OK
// let r3 = &mut s; // Mutable reference - Compile error!
println!("{} and {}", r1, r2);
let r3 = &mut s; // Now r1 and r2 are no longer used, so OK
r3.push_str(" world");rustLifetimes
Lifetimes are Rust’s mechanism for ensuring references are always valid. Most of the time, the compiler can infer them automatically, but sometimes explicit annotation is needed:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}rustHere, 'a tells the compiler: the returned reference lives at least as long as the shorter of the input references.
Lifetime Elision Rules
The compiler has three elision rules to automatically infer lifetimes:
- Each reference parameter gets its own lifetime
- If there’s exactly one input lifetime, it’s assigned to all output lifetimes
- If a method has
&selfor&mut self,self’s lifetime is assigned to all output lifetimes
Practical Example
Here’s a practical example that combines ownership concepts:
struct TextEditor {
content: String,
history: Vec<String>,
}
impl TextEditor {
fn new() -> Self {
TextEditor {
content: String::new(),
history: Vec::new(),
}
}
fn write(&mut self, text: &str) {
self.history.push(self.content.clone());
self.content.push_str(text);
}
fn undo(&mut self) {
if let Some(prev) = self.history.pop() {
self.content = prev;
}
}
fn content(&self) -> &str {
&self.content
}
}rustSummary
The ownership system is Rust’s foundation. Although you may frequently “battle” with the compiler when learning, once you understand these concepts, you’ll find they help you write safer and more efficient code. In the next article, we’ll explore Rust’s powerful error handling mechanisms.
Previous: Why Choose Rust?
评论区
文明评论,共建和谐社区