Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Ownership in the Rust Language

DZone's Guide to

Ownership in the Rust Language

In this post, we take a look at the concept of ownership in Rust, and how ownership plays out in our Rust code. Read on Rustaceans!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

One of the very distinctive features that makes Rust stand out from 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. That sotred data can then be utilized via 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 known as the garbage collector. A garbage collector is a process which monitors the program for any memory leak and takes care of 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. 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 a 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.

{
  // s is not valid here
  let s = "hello";  // s is valid from this point forward
  // do stuff with s
} // s is no longer valid

The variable to which the chunk of memory is allocated is known as the owner of the memory. The 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 the 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 must allocate memory at runtime, this is where we use heap. The required memory on a heap is traced by the OS, and, if it's available, is marked as in use. After this, we push the pointer to that heap memory 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;  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 Variables

In the code snippet given below:

let first_variable = 5;
let second_variable = first_variable;

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 to the stack.

let s1 = String::from("hello"); //This is how we create a string
                                // in Rust
let s2 = s1;

So when we try something similar to the integer example, instead of copying the string itself, we copy the identifier information that we pushed to 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 goes out of scope? Which memory location would its 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 goes out of scope.  drop() is called only 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 the case of parametric function calls. Passing a variable as a parameter to a function is treated similarly to the move shown above. Basically, when a String variable is passed as a parameter to a function, its validity within the current scope terminates by default.

fn main() {
    let s = String::from("hello");   // s comes into scope

    takes_ownership(s);             // s' value moves into the
                                   // function and so is no longer
                                  // valid here

    let x = 5;                   // x comes into scope

    makes_copy(x);              // x would move into the function,
                               // but i32 is Copy, so it’s okay to
                              // still use x afterward

} // Here, x goes out of scope, then s. But because s's value was
  // moved, nothing special happens.

fn takes_ownership(some_string: String) { // some_string comes into
                                          //scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called.
  // The backing memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

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

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web dev ,rust ,tutorial ,ownership

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}