How Rust manages its memory?
Recently I start learning Rust language and encounter with an interesting topic of Ownership in Rust. We'll talk about ownership as well as how Rust lays data out in memory.

Ownership is a set of rules that govern how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that regularly looks for no-longer-used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.
Ownership Rules
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
As a first example of ownership, we’ll look at the scope of some variables. A scope is the range within a program for which an item is valid. Take the following variable :-
fn main() {
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
}
In other words, there are two important points in time here:
- When
s
comes into scope, it is valid. - It remains valid until it goes out of scope.
So, Rust have different types of data types some of them have fixed length and some have dynamic. Rust will behave different for both types of data.
Fixed sized data type can be stored on the stack and popped off the stack when their scope is over, and can be quickly and trivially copied to make a new, independent instance if another part of code needs to use the same value in a different scope. But we want to look at data that is stored on the heap and explore how Rust knows when to clean up that data, and the String
type is a great example.
For those who don’t know String
type and string literals are two different data types in Rust string literals have fixed length while String
type have dynamic.
You can create a String
from a string literal using the from
function, like so:
fn main() {
let s = String::from("hello");
}
This kind of string can be mutated:
fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() appends a literal to a String
println!("{}", s); // This will print `hello, world!`
}
So, what’s the difference here? Why can String
be mutated but literals cannot? The difference is in how these two types deal with memory.

Memory and Allocation
In the case of a string literal, we know the contents at compile time, so the text is hard coded directly into the final executable. This is why string literals are fast and efficient. But these properties only come from the string literal’s immutability. Unfortunately, we can’t put a blob of memory into the binary for each piece of text whose size is unknown at compile time and whose size might change while running the program.
With the String
type, in order to support a mutable, growable piece 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
However, the second part is different. In languages with a garbage collector (GC), the GC keeps track of and cleans up memory that isn’t being used anymore, and we don’t need to think about it. In most languages without a GC, it’s our responsibility to identify when memory is no longer being used and to call code to explicitly free it, just as we did to request it. Doing this correctly has historically been a difficult programming problem. If we forget, we’ll waste memory. If we do it too early, we’ll have an invalid variable. If we do it twice, that’s a bug too. We need to pair exactly one allocate
with exactly one free
Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope. Here’s a version of our scope example from Listing 4–1 using a String
instead of a string literal:
fn main() {
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no
// longer valid
}
There is a natural point at which we can return the memory our String
needs to the allocator: when s
goes out of scope. When a variable goes out of scope, Rust calls a special function for us. 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.
Variables and Data Interacting with Move
Multiple variables can interact with the same data in different ways in Rust. Let’s look at an example using an integer in Listing 4–2.
fn main() {
let x = 5;
let y = x;
}
We can probably guess what this is doing: “bind the value 5
to x
; then make a copy of the value in x
and bind it to y
.” We now have two variables, x
and y
, and both equal 5
. This is indeed what is happening, because integers are simple values with a known, fixed size, and these two 5
values are pushed onto the stack.
Now let’s look at the String
version:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
}
This looks very similar, so we might assume that the way it works would be the same: that is, the second line would make a copy of the value in s1
and bind it to s2
. But this isn’t quite what happens.
Take a look at below picture to see what is happening to String
under the covers. A String
is made up of three parts, shown on the left: a pointer to the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents.

When we assign s1
to s2
, the String
data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to. In other words, the data representation in memory looks like below.

Wait….. What if the Rust like Figure 3 :-

Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop
function and cleans up the heap memory for that variable. But Figure 2 shows both data pointers pointing to the same location. This is a problem: when s2
and s1
go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.
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 being called a shallow copy, it’s known as a move. In this example, we would say that s1
was moved into s2
. So, what actually happens is shown in Figure 4.

That solves our problem! With only s2
valid, when it goes out of scope it alone will free the memory, and we’re done.
In addition, there’s a design choice that’s implied by this: 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.
Share among your peers until then keep exploring, keep learning and keep supporting!
Don’t forget to check out more articles