Understanding Ownership
Stack and the heap
- Data types that we know the size in advance are allocated on the stack. Examples are the primitive types: ints, floats, chars, bools, tuples containing primitive types,
- On the other hand, data types that we don't know the size in advance such as a String type or Vec
type are allocated on the heap. See stack vs heap for more info
Copying vs Moving
- Types that implement the Copy trait are copied when you assign them to a variable (or pass it as an argument to a function). For example:
fn main() { let number1 = 10; println!("number1 is {}", number1); let mut number2 = number1; println!("number2 is {}", number2); number2 = 97; println!("number2 is now {}", number2); println!("number1 is still {}", number1); }
- Types that do not implement Copy but instead implement the Drop trait are moved when you re-assign a variable of such type (or pass it as an argument to a function). This means the new variable you have assigned it to is now the new owner of that data. Examples of Rust built-in types that implement the Drop trait are String, Vec, File etc
fn main() { let fruit = String::from("apple"); println!("fruit is {}", fruit); //fruit is moved into fruit2 let fruit2 = fruit; println!("fruit2 is {}", fruit2); //cannot use fruit here //println!("{} is no longer in scope, it has been dropped", fruit); }
If you uncomment, the last line in the above snippet and run the snippet, you'll see error messages about moving.
Same thing happens for method calls:
fn main() { let fruit = String::from("apple"); println!("fruit is {}", fruit); let mut basket:Vec<String> = vec![]; println!("basket is {:#?}", basket); //fruit is moved into the function put_in_basket(fruit, &mut basket); println!("basket is {:#?}", basket); //cannot use fruit here //println!("{} is no longer in scope, it has been dropped", fruit); } fn put_in_basket(theFruit: String, basket: &mut Vec<String>) { basket.push(theFruit); }
If you uncomment, the last line in the above snippet and run the snippet, you'll see error messages about moving.
Ownership rules
There are three rules related to ownership in Rust namely:
- Every value in Rust has an owner
- There can be only one owner at a time
- When the owner goes out of scope, the data/value is dropped (Source: ownership rules )
Who owns what?
In Rust, ownership really only makes sense when you think about types which are allocated on the heap such as String
.
These types also implement the Drop
trait which allows such types to be cleaned up when they go out of scope.
On the other hand, primitive types such as integers (signed and unsigned), chars, bools, tuples
etc (as seen above ), are simply copied when you reassign variables of these types to a another variable or pass as method argument.
Ownership examples
Whenever you create an object on the heap and assign that object to a variable, the owner of that object is the variable you assigned it to.
To understand ownership, we need to use a type like String
which is allocated on the heap.
fn main() { let name = String::from("Olu Shiyanbade"); }
In the above, we build a new String
which is allocated on the heap. The location of that String
is
assigned to the variable name
which is in turn allocated on the stack. In this statement, variable name
is said to be
the owner of the String
.
Changing ownership
fn main() { let name = String::from("Olu Shiyanbade"); println!("In main, name is {}", name); let another_name = name; println!("In main, another_name is {}", another_name); //Can no longer use name because the owner is now variable `another_name` //println!("In main, name is {}", name); print_name(another_name); //Can no longer use another_name because the ownership was moved into the //function's paramater when it was passed as an argument above. // Thus the owner is now the function parameter `name` //println!("name is {}", another_name); } fn print_name(name: String) { println!("In print_name, name is: {}", name); }
In the above,
- the String
Olu Shiyanbade
is initially owned by thename
variable. - the snippet
let another_name = name;
assignsname
toanother_name
. At this point, we sayname
is moved intoanother_name
andanother_name
becomes the owner of the StringOlu Shiyanbade
while variablename
goes out of scope (i.e. it is dropped). - similarly, when we do
print_name(another_name
,another_name
is moved into theprint_name
function and that function'sname
parameter becomes the new owner. At this pointanother_name
goes out of scope and can no longer be used beyond that point.