Rust Ownership and Borrowing
Ownership is Rust's special way of managing memory safely without a garbage collector.
Why Ownership?
Programs need memory to store data. Some languages make you free memory yourself, which leads to bugs. Others use a garbage collector, which slows things down. Rust uses a third way: a set of ownership rules that the compiler checks.
The Three Rules
- Each value in Rust has one owner (a variable).
- There can be only one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory is freed).
Example
fn main() {
{
let city = String::from("Karachi");
println!("{}", city);
} // city goes out of scope here and its memory is freed
// println!("{}", city); // error: city does not exist here
}Karachi
Moves
When you assign a String to another variable, the ownership moves. The first variable can no longer be used.
Example
fn main() {
let a = String::from("Workownix");
let b = a;
// println!("{}", a); // error: value borrowed here after move
println!("{}", b);
let c = b.clone();
println!("{} and {}", b, c);
}Workownix Workownix and Workownix
clone() makes a full, separate copy, so both variables are valid.
Simple types like integers, floats, bool and char are Copy types. They are copied, not moved, so let y = x; keeps x usable.
Ownership and Functions
Passing a String to a function also moves it:
Example
fn show(text: String) {
println!("{}", text);
}
fn main() {
let name = String::from("Ayesha");
show(name);
// show(name); // error: name was moved into the first call
let n = 5;
let m = n;
println!("{} {}", n, m);
}Ayesha 5 5
Borrowing with References
Moving every value into functions would be annoying. Instead, you can borrow a value with a reference, written &. The function can read it, but the owner keeps it.
Example
fn count_letters(text: &String) -> usize {
text.len()
}
fn main() {
let course = String::from("Rust");
let size = count_letters(&course);
println!("{} has {} letters", course, size);
}Rust has 4 letters
Mutable References
To let a function change a borrowed value, use &mut. The variable must also be mut.
Example
fn add_title(name: &mut String) {
name.push_str(" Sahab");
}
fn main() {
let mut teacher = String::from("Imran");
add_title(&mut teacher);
println!("{}", teacher);
}Imran Sahab
The Borrowing Rules
At any one time you can have either:
| Allowed | Example |
|---|---|
| Any number of shared references | &x, &x, &x |
| Exactly one mutable reference | &mut x |
You cannot have both at the same time. This rule stops two parts of the program from changing the same data at once, a common cause of bugs.
Slices
A slice borrows part of a collection, without copying it:
Example
fn main() {
let text = String::from("Hello Karachi");
let first = &text[0..5];
let last = &text[6..];
println!("{} / {}", first, last);
let marks = [60, 70, 80, 90];
let middle = &marks[1..3];
println!("{:?}", middle);
}Hello / Karachi [70, 80]
Exercise
Exercise
After let a = String::from("hi"); let b = a; which variable can you still use?
