Rust Lang

How to Work with Ownership in Rust

In this article, we will explore Rust ownership and how it works. The ownership and borrowing approach is a relatively new construct and trips new-beginners. In this guide, we will attempt to demystify how ownership works.

What Is Ownership?

Traditionally, there are two fundamental ways to manage memories. The first one is garbage collectors; it is mainly used in high-level languages that abstract the concept of memory management from the programmer.

The second is “manual” memory management, where the programmer explicitly defines memory usage. Although it provides control, it leaves much room to shoot yourself in the foot.

Rust takes on an alternative approach called ownership and borrowing. Ownership is a new “construct” that defines a value has its owner.

The following are the rules of ownership in Rust:

  1. Any value defined in a Rust program has an owner.
  2. A value can have one owner at a time.
  3. A value can only live as long as its owner is alive. Meaning the owner has not been dropped.

To better understand Rust ownership, let us discuss a few concepts and examples.

Scope

We can define scope as a range under which a specified value lives. The scope plays an important part in ownership and borrowing.

Each variable is bound by its scope. In this case, a scope refers to a block as enclosed by a pair of curly braces.

Consider the following example program:

fn main() {
    // defined in the scope of the main function
    let long_life = 100;
    {
        // defined in a inner block (new scope)
        let short_life = 10;
        println!("Inner scope: {}", short_life);
    }
    // short_life does not exist in this scope
    println!("Outer scope: {}", short_life);
    println!("Long Life: {}", long_life);
}
fn new_func() {
    println!("Function scope: {}", long_life);
}

In the previous example program, we have two functions: the main function and the new_func function.

Inside the main function, we declare a new variable called long_life. We also create a new scope and declare a new variable called short_life.

If you run the previous code, the Rust compiler will tell you that the variables, short_life and long_life, do not exist in that scope.

This is because the variable short_life is declared in a new scope. When that scope ends, the variable is dropped. This means that the variable short_life does not exist outside the curly braces.

The same case applies to the long_life variable. It is only accessible in the main function. Attempting to access it in another function is not allowed.

Shadowing

Variable shadowing is a typical case. It refers to a feature where the first variable declaration is “overwritten” or shadowed by another.

Take the following example code:

fn main() {
    let var1 = 10;
    println!("Before shadow: {}", var1);
    // after shadow
    let var1 = 100;
    println!("After shadow: {}", var1);
}

Here, we have a variable called var1 set to 10. Then, we shadow it by declaring the variable with the same name and set it to a new value.

This should print:

NOTE: Do not confuse shadowing with mutability. Check our tutorial on Rust mutability to learn more.

Transfer Ownership

Rust allows us to transfer the ownership of value using copy or move operations. Take the following example code:

fn main() {
    let var1 = 10;
    let var2 = var1;
    println!("var1: {}", var1);
    println!("var2: {}", var2);
}

The previous example declares a variable, var1, and assigns it the value 10. We also declare a new variable, var2, and assign it to var1.

When this happens, Rust will create a new copy of the value and assign it to var2. This means that both variables do not share the value, and each occupies its own memory space.

What happens when we attempt to perform the previous operation on a value with a non-deterministic memory size?

Take a string type, for example. Since it can grow or shrink, there is no pre-determined memory size. Hence, it does not implement the copy trait:

let str1 = String::from("hi");
    let str2 = str1;
    println!("str1: {}", str1);
    println!("str1: {}", str2);

Running the same operation forces the Rust compiler to move the value of str1 to str2. Since a value can have only one owner at a time, the variable str1 is no longer valid. This will cause the compiler to return an error if we attempt to access it:

Conclusion

One reason Rustaceans love the Rust programming language is ownership. It is a new feature that offers more flexibility and security at the same time. Understanding how it works can be beneficial to creating fast and efficient applications. Plus, the rules of ownership and concepts were discussed. We hope you found this article helpful. Check the other Linux Hint articles for more tips and information.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list