RUST – Ownership

One of the very distinctive features that make Rust stand out among the other languages is the way it does memory management.

Memory management in C happens by either declaring a variable or by allocating a chunk of space at runtime. The same then can be freed using explicit function calls. So basically the control resides with the developer which may result in error-prone code. In languages like Java, there is a concept of a garbage collector. A garbage collector is a process which monitors the program for any memory leak and frees it. The memory is said to be eligible for garbage collection if there remains no reference to it or if it’s in the Island of isolation. A garbage collector is always running in the background and can not be explicitly controlled to free memory, which is where things may get a little out of hand.

Ownership, on the other hand, is different from the aforementioned. It doesn’t require you to explicitly deal with the memory or slow the program down while handling it. It works around the concept of scope.

A scope is the range of line of code within which an item is valid. Generally, we mark the scope using  { curly braces }. So a variable, for example, will be valid from the point of declaration until the end of its scope.

The variable to which the chunk of memory is allocated is known as the owner of the memory. Principles of ownership state that there can be only one owner at a particular time, and whenever the owner goes out of the scope, the respective memory is eligible to be returned to the OS.

There are two types of memory which are accessible to the code: Stack and Heap. Generally, when the data in use is of a fixed known size, we push it in the stack. Since stack always accesses data on the “top”, this whole process becomes faster and efficient. But there are times when the fixed size is over the limit of the stack or perhaps we require to allocate memory at runtime, this is where we use the Heap. The required memory on the heap is traced by the OS, and if it’s available, its marked as in use. After this, we push the pointer to that heap memory on to the stack. Since this requires us to reach the memory using a pointer, the whole process is slower. So when we call a function, the parameter values, local variables, and the pointers to heap are all pushed onto the stack.

So whenever a variable leaves its scope, it gets popped off and things become pretty simple for data on the stack. The memory is returned to the OS once the owner goes out of scope. However, things are different when data is stored on the heap. Rust has a special method called drop which does the deed of returning the memory acquired by the owner in the heap to the OS, the drop is called whenever rust encounters the closing ‘}’.

Now, this may seem a simple change but it has a profound impact on how rust deals with things.

Move: Interaction between data and variable

In the code snippet given below:

We bind first_variable to a memory location which refers to the value 5, and then we create a copy of the value in first_variable and bind it to second_variable. Since integers are of a constant size and can easily be accommodated to the stack, this is as simple as it seems. However, when you consider complex data types such as a string, things are a little different. The string may be known during runtime, for example, a user input, hence its best to allocate it the memory on the heap. To identify this memory on the heap, we use three details i.e. the pointer to the heap, the length, and the capacity. This information is pushed on to the stack.

rust-table

So when we try something similar to the integer example, instead of copying the string itself, we copy the identifier information we push onto the stack. So now there are two variables referring to the same memory location. Now, this is where things get interesting. What if s1 goes out of scope? It should call the drop() method, right? Great. Now, what happens to s2? Which memory location would it refer to? What happens now when s2 would go out of scope? Which memory location would its drop() call free?

This is where Rust plays it differently. As soon as a second variable points to the same location, Rust invalidates the previous variable. So in the case above, s1 becomes invalid as soon as s2 comes into the picture. Hence nothing happens when s1 would go out of scope, drop() would only be called when s2 goes out of scope. So instead of a shallow copy, this can be referred to as a move.

Passing parameters in functions

Another interesting outcome of this ownership situation is the way it behaves in case of parametric function calls. Passing a variable as a parameter to a function is treated similarly to the move as shown above. So basically when a String variable is passed as a parameter to a function, its validity within the current scope terminates by default.

This was a brief introduction to the ownership paradigm of Rust. Feel free to provide your feedback on this blog or to initiate a discussion in the comment section.

References:
https://doc.rust-lang.org/book/2018-edition
knoldus-advt-sticker

Written by 

Ayush Prashar is a software consultant having more than 0.5 years of experience. He is familiar with programming languages such as Java, Scala, C, C++ and he is currently working on reactive technologies like Lagom, Akka, Spark, and Kafka. His hobbies include playing table tennis, basketball, watching TV series and football.

Leave a Reply

%d bloggers like this: