Rust is a system programming language known for its focus on memory safety; ownership is a fundamental concept that sets it apart from other programming languages. As someone coming from a JavaScript, TypeScript, and Go background, the concept of ownership was very hard for me to grasp. It's one of the things that sets Rust apart from any other language.
Ownership is a set of rules that govern how Rust programs manage memory. Rust's ownership system is designed to prevent common memory-related bugs such as null pointer dereferencing, use-after-free errors, and data races.
In this series of articles, we will embark on a journey to understand Rust's ownership. We will start with memory management and how it relates to computer programs. We will go through stack and heap memory and how they help us understand ownership in Rust. We will also learn the ownership rules with example programs and visual explanations to make them stick.
Understanding ownership lays a strong foundation for comprehending the distinctive features that set Rust apart.
Computer programs and memory
For programs to run, they are loaded into the computer's memory (RAM), and they must manage how they use the computer's memory when they are running.
For example, in this simple Rust program:
fn main() {
let width = 30;
let height = 50;
let area = width * height;
println!(
"The area of the rectangle is {} square pixels.",
area
);
}
The main function has some local variables that it must access when the program runs. The variables and what they bind to must be stored in memory (referred to as memory allocation) for the duration of the function, i.e., when they are being used. After the function finishes execution, the variables should be 'removed' from memory (referred to as memory deallocation) as they are not needed again.
Programming languages have to ensure that they only keep the data that will still be needed for the program to run in memory, as computer memory is not infinite. To understand rust ownership, we must have an understanding of how memory works, with a stack and a heap, and the types of data that are stored in each.
Before that, let's understand the difference between Value Types and Reference Types.
Value types vs Reference types
Value Types in Rust Explained
In the sample program given above, the variables width
,height
and area
all bind to an integer, which is a value type. Value types are types where the actual value is stored in a variable. Most importantly, value types are always copied by value.
Let's understand what that means with an example:
fn main() {
let width1 = 30;
let width2 = width1;
}
Here we have variable width1
that is bonded to the value 30
, this variable is then assigned to a new variable width2
. Because width1
is a Value Type, it is copied by value when we assign it to width2
:
This means that if we change the value of either of the variables, the value of the other variable is not affected. Let's see an example of that by modifying width1
(we have to make it mutable to achieve this)
fn main() {
let mut width1 = 30;
let width2 = width1; // width1 is copied by value to width
width1 = 45; // modifying value of width1 won't change the value of width2
println!(
"width1 = {}, width2 = {}",
width1, width2
);
}
In the example above, the value of width1
was modified after it was assigned to width2
but because we are dealing with Value Types that are copied by value, the change in width1
does not affect the value of width2
.
Another important thing to note about Value Types is that they are usually stored on Stack Memory. Don't worry if that term is foreign. We will learn about what it means shortly.
Some examples of Value Types in Rust that are usually stored on Stack Memory are:
Scalar Types e.g integers, floating-point numbers, Booleans, and characters
Compound Types e.g tuples and arrays
These types have a known size that is fixed, and they can be easily stored on the stack and popped off.
Reference Types in Rust Explained
Reference types, on the other hand, store a reference (memory address) to the location where the data is actually stored. Reference types are copied by reference. This means that when you assign a reference type to another variable, both variables point to the same location in memory.
In this example, both var_1
and var_2
point to the same data in memory. If you modify the data via one of the variables, the change will be reflected when you access it through the other variable.
Unlike Value Types which are usually stored on the stack, Reference Types are stored on another kind of memory that we will learn about shortly, the Heap Memory.
Examples of some Rust types that are stored on the Heap are as follows:
String: dynamically sized, growable strings
Vec<T>: dynamically sized , growable array
Box<T>
Note: 'dynamically sized' means the size can change, i.e., it is dynamic. It means the data type involved can grow in size. This implies that they don't have a fixed size i.e they cannot be stored on the Stack Memory
Memory Management
In a Systems Programming language like Rust, how the language behaves is affected by whether a value is on the Stack or Heap. Where the value is stored also determines the kind of assumptions and decisions you can make about it.
What then are Stack and Heap memory? To understand what they mean, we need to understand Computer Memory itself. We will not be going into the details of computer memory. The model we will be exploring will not be 100% accurate. But it will be sufficient to help us understand Ownership in rust without getting bogged down with details.
Your operating system provides your program with a relatively straightforward view of memory: a vast range of addresses, starting from 0 up to a high number, corresponding to the amount of RAM your computer possesses. For instance, if you have a gigabyte of RAM (1GB), your addresses span from 0 to 1,073,741,823. This figure stems from 2^30, the number of bytes in a gigabyte. Subtracting 1 from 2^30 gives 1,073,741,823.
We will represent the address in hexadecimals so that we don't confuse it with normal numbers. 1,073,741,823 in hexadecimal is 0x3FFFFFFF.
This memory model looks like a giant array of slots that are next to each other, like an array of drawers in which you can store things. The address usually starts at zero and goes up to the final number.
The Stack and Heap are parts of memory available for your code to use for execution. They are structured in different ways, and the difference in structure determines how you can allocate memory, deallocate memory, or access values stored on either of them. We will learn more about Stack and Heap in the next article of this series.
Conclusion
In this first part of the series on Rust Ownership, I introduced some of the foundation concepts needed to understand Ownership in Rust. We went through the difference between Value and Reference types. We learned that Value Types are usually stored in a part of memory known as the Stack, while Reference Types are stored on the Heap.
We also learned about a simple model of computer memory; that can be thought of as a large array of memory locations with addresses.
In the next part of this series, we will be learning about Stack and Heap memory, what they are and how data is allocated to them.