Chapter 4 Understanding Ownership
Ownership
In Rust, memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile.
Ownership Rules
Ownership rules:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Variable Scope
A scope is the range within a program for which an item is valid.
The String Type
String type manages data allocated on heap (compare to string literal).
It is able to store amount of text which is unknown at compile time.
It can be mutated (the string literal cannot).
Memory and Allocation
In Rust, the memory is automatically returned once when the variable is out of scope.
Rust uses move rather than shallow copy or deep copy when apply assignment on variable that allocated on heap. Here is an example.
let s1 = String::from("hello");
let s2 = s1; // the s1 is moved to s2, s1 is invalid.
println!("{s1}, world!"); // compile error! borrow of moved value `s1`Rust will never automatically create deep copies of you data.
Cloning Data
Using the clone method when we do want to deeply copy the data on heap.
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1: {s2}, s2: {s2}");Rust has a special annotation: the Copy trait. We can place on types are stored on the stack (like integer).
If a type implement the copy trait, variable that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
The Copy trait and Drop trait (which control the memory free) is contradicted. Rust doesn’t allow them to be implemented on the same type.
Ownership and Functions
Passing a variable to a function is similar to assigning a value to a variable. The variable will be moved or copied, depending on the types.
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // because i32 implements the Copy trait,
// x does NOT move into the function,
// so it's okay to use x afterward
}Return Values and Scope
Returning values can also transfer ownership.
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into takes_and_gives_back, which also moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing happens. s1 goes out of scope and is dropped.
fn gives_ownership() -> String { // gives_ownership will move its return value into the function that calls it
let some_string = String::from("yours"); // some_string comes into scope
some_string // some_string is returned and moves out to the calling function
} // this function takes a String and returns a String
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into scope
a_string // a_string is returned and moves out to the calling function
}They all follow the same pattern:
- Assigning a value to another variable moves it.
- When a variable that includes data on the heap goes out of the scope, the value will be cleaned up by drop unless the ownership is transferred to another variable.
Reference and Borrowing
A reference is like a pointer (in that it’s an address we can follow) to access a data. The data is owned by another variable.
With reference, we can use a value without transferring its ownership.
Here is an example.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{s1}' is {len}.");
}
fn calculate_length(s: &String) -> usize {
s.len()
}The following diagram depicts the concept of reference in this example.

We call the action of creating a reference borrowing.
Like the variable, the reference is immutable by default.
Mutable References
Using the mutable reference to make the borrowed value modifiable.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}The mutable references in Rust is monopoly. That is, a value who has a mutable reference cannot have any other reference.
Rust uses this rule to prevent data races at compile time.
Moreover, we also cannot create a mutable borrowing when the value is already having a immutable reference.
Multiple immutable references to a value are allowed.
The Slice Type
Slice allows reference a contiguous sequence of elements in a collection.
We can create slices using the range within the square brackets [starting_index..ending_index], where the starting_index indicate the first position and the ending_index is one more than the last position in the slice.
If the starting_index is 0, we can drop it [..ending_index]. By the same token, if the slice includes the last item, we can drop the trailing number. Dropping both the starting and ending index will slice the whole collection.