Rust Ownership

Posted on Aug 2, 2021

Understanding Ownership

Ownership is what enables Rust to make memory safety guarantees without needing a garbage collector.

What is a Garbage collector?

Garbage collection (GC) is a form of automatic memory management.

It attempts to reclaim memory which was allocated by the program, but is no longer used — hence the name garbage.

GC exists to relieve the programmer from manually managing memory, which can be susceptible to errors such as memory leaks.

But garbage collection consumes resources when deciding which memory to free, which leads to decreased performace.

Some programming languages, like Python or C#, have garbage collection that looks for no longer used memory. In other languages, such as C and C++, the programmer must allocate and deallocate the memory manually.

Rust uses a different approach — memory is managed by a system of ownership with a set of rules that are checked by the compiler (rustc). The Ownership features don’t slow down the program when it’s running.

Stack vs Heap

Both the stack and the heap are parts of memory available to store data at runtime, but they are structured in different ways.

In order to understand Ownership in Rust and how to take advantage of it, we first must understand the paradigms of allocating data in the Stack versus in the Heap.


Stack

  1. It is used by default on Rust.
  2. It is faster than allocating memory in the heap.
  3. Variables have a known fixed size.
  4. Memory is retrieved after variables go out of scope.

Example:

After the variable goes out of scope, it is not valid anymore:

fn main() {			// x is not valid here, it's not declared yet.
	
	let x = 2;		// integer x is declared, x is valid from now on.
	
	print_x();	    // this runs the function "print_x" that is not
					// on the "main" function scope.

}					// the scope ends here, x is not valid anymore.

fn print_x(){
	println!("{}", x);
}

If we try to run this we will get the following error:

error[E0425]: cannot find value `x` in this scope
  --> main.rs:10:20
   |
10 |     println!("{}", x);
   |                    ^ not found in this scope

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.

Another example:

Every variable in the stack has a known fixed size:

fn main() { 
    let stack_int: i32 = 10;		// 32 bit integer to the stack
    let stack_float: f64 = 20.;		// 64 bit float to the stack
    let stack_bool: bool = true;	// bool variable to the stack (true or false)
    let stack_char: char = 'a';		// char data type to the stack (fixed size)
	let stack_str: &str = "hello";	// &str (string that consists of unsigned 8 bit integers)
}

Learn more about strings in Rust at: https://doc.rust-lang.org/rust-by-example/std/str.html


Heap

  1. Slower compared to allocating memory to the stack.
  2. Variables can grow in size (Vector, String, etc).
  3. Memory can live beyond the scope that created it.
  4. Memory is retrieved when the Owner goes out of scope.
let x = 22; 
/*  |    |
    |    +--> Value
    V     
  Owner				*/

Example:

When the Owner goes out of scope, the memory is retrieved:

fn main() {
	let heap_string: String = String::from("Hello");	// Heap String (vector of bytes)

	let heap_string_2 = heap_string;   /* heap_string value is assigned to heap_string_2
									      a copy is not created because the ownership of
										  heap_string is transferred to heap_string_2,
								   	      and heap_string gets destroyed (out of scope) */
	println!("{}", heap_string);
	println!("{}", heap_string_2);
}

This will print an error because heap_string Ownership is moved to heap_string_2:

error[E0382]: borrow of moved value: `heap_string`                                           
 --> main.rs:6:17                                                                            
  |                                                                                          
2 |     let heap_string: String = String::from("Hello");                                     
  |         ----------- move occurs because `heap_string` has type `String`, which does not i
mplement the `Copy` trait                                                                    
3 |                                                                                          
4 |     let heap_string_2 = heap_string;                                                     
  |                         ----------- value moved here                                     
5 |                                                                                          
6 |     println!("{}", heap_string);                                                         
  |                    ^^^^^^^^^^^ value borrowed here after move                            
                                                                                             
error: aborting due to previous error                                                        
                                                                                             
For more information about this error, try `rustc --explain E0382`.

Ownership rules

  1. Each value in Rust has a variable called its owner
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value won’t be valid

NOTE: In the stack, because memory is “cheap”, you can have more than one owner pointing at the same value.

Learn more about Ownership at: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html