Ownership
## Learning Objectives
- Understand Rust's ownership rules
- Master borrowing with references
- Learn lifetime basics
- Avoid common ownership pitfalls
## What is Ownership?
Ownership is Rust's unique system for managing memory. Every value has a single owner, and when the owner goes out of scope, the value is dropped.
## Ownership Rules
1. Each value has exactly one owner
2. There can only be one owner at a time
3. When the owner goes out of scope, the value is dropped
```rust
fn main() {
let s1 = String::from("hello"); // s1 owns the string
let s2 = s1; // Ownership moves to s2
// println!("{}", s1); // Error: s1 is no longer valid
println!("{}", s2); // OK: s2 owns the value
}
```
## Move Semantics
### Strings vs Literals
```rust
// String (heap-allocated) - MOVES
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2
// s1 is now invalid
// String literals (&str) - COPIES ( Clone trait)
let s1 = "hello";
let s2 = s1; // s1 is copied to s2
println!("{} {}", s1, s2); // Both valid
```
### Integer Types - Always Copy
```rust
let x = 5;
let y = x; // Copy (i32 implements Copy)
println!("{} {}", x, y); // Both valid
```
## Clone and Copy
### Clone (Deep Copy)
```rust
let s1 = String::from("hello");
let s2 = s1.clone(); // Deep copy of heap data
println!("{} {}", s1, s2); // Both valid
```
### Copy Trait (Stack Data)
```rust
// Types that implement Copy (stack-only):
// - All integer types
// - All floating-point types
// - Boolean
// - Char
// - Tuple of Copy types
// - Array of Copy types
let x = (1, 2, 3);
let y = x; // Copy, both valid
```
## Ownership and Functions
### Passing Values to Functions
```rust
fn main() {
let s = String::from("hello");
takes_ownership(s); // s's value moves into function
// println!("{}", s); // Error: s is invalid
let x = 5;
makes_copy(x); // x is copied (i32 is Copy)
println!("{}", x); // OK: x is still valid
}
fn takes_ownership(s: String) {
println!("{}", s);
} // s is dropped here
fn makes_copy(x: i32) {
println!("{}", x);
} // x is dropped here
```
### Return Values and Scope
```rust
fn main() {
let s1 = gives_ownership(); // Function returns ownership
let s2 = String::from("hello");
let s3 = takes_and_returns(s2); // s2 moves in, s3 is returned
}
fn gives_ownership() -> String {
let s = String::from("hello");
s // Returns and moves to caller
}
fn takes_and_returns(s: String) -> String {
s // Returns and moves to caller
}
```
## Borrowing
### References
A reference lets you access a value without taking ownership.
```rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Pass reference to s1
println!("{} has length {}", s1, len); // s1 still valid
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but NOT dropped
```
### Mutable References
```rust
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
fn change(s: &mut String) {
s.push_str(", world");
}
```
### Borrowing Rules
1. One mutable reference OR any number of immutable references
2. References must always be valid (no dangling references)
```rust
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK: multiple immutable refs
// let r3 = &mut s; // Error: cannot borrow mutably while immutable refs exist
println!("{} {}", r1, r2);
// r1 and r2 no longer used after this point
let r3 = &mut s; // OK: immutable refs are done
r3.push_str(" world");
```
## Lifetimes
### What are Lifetimes?
Lifetimes are annotations that help the compiler ensure references are valid.
```rust
fn main() {
let r;
{
let x = 5;
r = &x; // Error: x doesn't live long enough
}
println!("{}", r);
}
```
### Lifetime Annotations
```rust
// &'a means reference lives at least as long as lifetime 'a
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 string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result);
}
// result would be invalid here if used
}
```
### Lifetime Elision
The compiler uses rules to infer lifetimes in common cases:
```rust
// These are equivalent:
fn first_word(s: &str) -> &str { }
fn first_word<'a>(s: &'a str) -> &'a str { }
// Rule 1: Each reference parameter gets its own lifetime
// Rule 2: If there's exactly one input lifetime, it's applied to output
// Rule 3: If there's a &self or &mut self, it's applied to output
```
### Static Lifetime
`'static` means the reference lives for the entire program:
```rust
let s: &'static str = "I live forever";
```
String literals have `'static` lifetime.
## The Slice Type
Slices are references to a portion of a collection:
```rust
let s = String::from("hello world");
let hello = &s[0..5]; // Same as &s[..5]
let world = &s[6..11]; // Same as &s[6..]
// String slice
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
```
## Summary
- Each value has exactly one owner
- When ownership moves, the original variable is invalidated
- Copy types (primitives) are copied instead of moved
- Clone creates a deep copy
- References borrow values without taking ownership
- Mutable reference allows modification of borrowed value
- Only one mutable reference OR multiple immutable references
- Lifetimes ensure references are valid as long as needed
- Slices are references to portions of data
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →