Rust Basics - Ownership, Borrowing & Lifetimes
Rust Basics - Ownership, Borrowing & Lifetimes
These three concepts form the foundation of Rust's memory safety guarantees. Understanding them is key to writing Rust code.
Ownership
Ownership is Rust's most unique feature. It enables memory safety without a garbage collector.
The Rules
- Each value has one owner
- Only one owner at a time
- Value is dropped when owner goes out of scope
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1's value moves to s2
// println!("{}", s1); // ERROR! s1 is no longer valid
println!("{}", s2); // OK
}
Move Semantics
fn take_ownership(s: String) {
println!("{}", s);
} // s is dropped herefn main() {
let s = String::from("hello");
take_ownership(s);
// s is no longer valid here
}
Copy Types
Primitive types implement Copy, so they're copied instead of moved:
let x = 5;
let y = x; // x is copied, not movedprintln!("{}", x); // OK!
println!("{}", y);
Borrowing
Instead of transferring ownership, we can borrow values:
Immutable References
fn calculate_length(s: &String) -> usize {
s.len()
}fn main() {
let s = String::from("hello");
let len = calculate_length(&s);
println!("'{}' length is {}", s, len); // s is still valid!
}
Mutable References
fn change(s: &mut String) {
s.push_str(", world");
}fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
The Rules of References
- One mutable reference OR any number of immutable references
- References must always be valid
let mut s = String::from("hello");let r1 = &s; // OK
let r2 = &s; // OK - multiple immutable
// let r3 = &mut s; // ERROR! Cannot borrow as mutable
println!("{} {}", r1, r2);
let r3 = &mut s; // OK - r1 and r2 are no longer used
Lifetimes
Lifetimes ensure references are valid as long as we need them.
Lifetime Annotation Syntax
&i32 // a reference
&'a i32 // a reference with explicit lifetime
&'a mut i32 // a mutable reference with explicit lifetime
Function with Lifetimes
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");
{
let s2 = String::from("x");
let result = longest(&s1, &s2);
println!("{}", result);
}
}
Struct with Lifetimes
struct ImportantExcerpt<'a> {
part: &'a str,
}fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let i = ImportantExcerpt {
part: first_sentence,
};
}
Lifetime Elision
Rust can often infer lifetimes:
// These are equivalent:
fn first_word(s: &str) -> &str { }
fn first_word<'a>(s: &'a str) -> &'a str { }
Common Patterns
Returning Owned Data
When in doubt, return owned data:
fn get_string() -> String {
String::from("owned")
}
The Clone Trait
Explicitly clone when needed:
let s1 = String::from("hello");
let s2 = s1.clone(); // Deep copy
Summary
| Concept | Purpose | Syntax |
|---------|---------|--------|
| Ownership | Who owns the data | let s = String::from("hi") |
| Borrowing | Temporary access | &s, &mut s |
| Lifetimes | Ensure references valid | <'a> |
Master these, and you'll write safe, efficient Rust code!