Rust 003 — Ownership Rules
Rust uses a new approach for memory management: Memory is managed through a system of ownership with a set of rules that the compiler checks.
Stack:
The stack stores values in the order it gets them and removes the values in the opposite order (LIFO).
Heap:
The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap and is sometimes abbreviated as just allocating.
Pushing to the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data; that location is always at the top of the stack.
When your code calls a function, the values passed into the function (potentially pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.
Ownership Rules:
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
With the String
type, which is dynamic in nature, in order to support a mutable, growable peice of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:
- The memory must be requested from the memory allocator at runtime.
- We need a way of returning this memory to the allocator when we’re done with our
String
.
Rust calls a special function for us when a variable goes out of scope. This function is called drop
, and it’s where the author of String
can put the code to return the memory. Rust calls drop
automatically at the closing curly bracket.
If you’ve heard the terms shallow copy and deep copy while working with other languages, the concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy.
But because Rust also invalidates the first variable, instead of calling it a shallow copy, it’s known as a move.
Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.
The reason is that types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make.
References:
References allow us to move the values without transferring the ownership.
The &s1
syntax lets us create a reference that refers to the value of s1
but does not own it.
We call the action of creating a reference borrowing. We’re not allowed to modify something we have a reference to.
Mutable references have one big restriction: you can have only one mutable reference to a particular piece of data at a time.
Dangling References
The ownership rules in Rust don’t allow dangling pointers. The below code would complain during compilation because the s is invalidated after the dangle function scope ends and any reference to it is also invalid.
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from(“hello”);
&s
}
Reference:
