Skip to main content

References and Borrowing

Ownership

Rust has it's own way of managing memories that are allocated on the heap. Unlike C, where the burden of allocating and freeing the memory that is allocated on the heap falls on the shoulder of the programmer, Rust manages the memory for you as long as you follow it's ownership conventions.

This is how Rust deal with dynamically allocated memory on the heap. When you allocate data on the heap, that piece of data will be associated with a variable name. When the scope of that variable ends, and naturally (most of the time) the data on the heap associated with that variable will need to be freed, and Rust does that for you automatically:

{
	let s = String::from("Hello"); // s is valid from this point forward
}	// Scope of s is over, s is no longer valid, and the memory is returned to the allocator

Whenever a variable goes out of scope, if it has its memory allocated on the heap, it will be called a special function called drop when the variable goes out of scope. It will free the memory that is allocated for that variable back to the allocator, Rust will call it for you automatically at the closing curly bracket (the end of a scope).

Data move

Let's look at some form of alias and copying in Rust:

let x = 5;
let y = x;

This will do what you expect, the value of x is copied over to y. If you change y it will not affect the value of x.

Now let's look at data allocated on the heap.

let s1 = String::from("Hello");
let s2 = s1;

If we do this, we assume that s2 is an alias which points to the same string that's allocated on the heap. That will be true for language that C or Python, but in Rust it is different. When you make an alias to another data allocated on the heap, that pointer will be moved from s1 to s2, so s1 will no longer be valid after line number 2! This is due to the design of Rust because like we have mentioned earlier, when a variable goes out of scope a special drop function is called to free up the memory allocated for the data, if there is two variables that points to that allocated data, then there is going to be a double free error!

To resolve this, Rust does variable move, so when you do s2 = s1, s1 will no longer be valid afterward, so Rust only needs to worry about freeing up s2 and doesn't have to worry about freeing s1 anymore.

This is called move, Rust will invalidate the first variable after you do the assignment.Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap.This is wrong!

Clone

However, if you want to clone the data allocated on the heap you can call the clone method.

let s1 = String::from("hello");
let s2 = s1.clone();

Now s2 will also contain a copy of the heap data pointed by s1.

What about variable on the stack?

If you use say primitives in Rust like an integer like so

let x = 5;
let y = x;

Both x, y are still valid after running line number 2, why? This is because primitive's data size like integer is known at compile time and are stored entirely on the stack, copying the actual value are quick and easy to make, so there is no reason to invalidate x afterward.

Function and ownership

If you decide to pass a variable data that's allocated on the heap to a function let's say, it will also carry out move, which means the variable that you pass into the function will no longer be valid after you do the function call like so:

fn main() {
	let s = String::from("hello");
    
    take(s); // s is no longer validate after
    
    let x = 5;
    
    cant_take(x); // x is still valid because it is a copy, not a move
}

fn take(input: String) {
	// do something
}

fn cant_take(num: i32) {
	// do something
}