Ownership in Rust

Ownership in Rust

Introduction

Ownership is a unique Rust-only concept, that keeps Rust Programs safe. And as the Rust Book says Safety is the Absence of Undefined Behavior.

To give an example this program is safe.

fn main() {
    let x = 5;
    println!("{x}");
}

And this program is unsafe.

fn main() {
    println!("{x}");
    let x = 5;
}

Because we're trying to print x before declaring it.

error[E0425]: cannot find value `x` in this scope
 --> src/main.rs:2:16
  |
2 |     println!("{x}");
  |                ^ not found in this scope

For more information about this error, try `rustc --explain E0425`.

And the compiler describes the exact problem.

What Is Ownership? And Why Is It So Special?

Ownership is Rust's unique way of managing memory.

Generally Programming languages manage memory in one of two ways:

  1. Gives the Programmer low-level control over memory, and the Programmer allocates and deallocates memory in their Program. Languages like C and C++ work in this way.

  2. Memory is managed in the Program automatically through the Garbage Collector or GC in short, and the Programmer never really has to think too much about memory. High-level languages like JavaScript, Python, C# work in this way.

Both of these methodologies have their Pros and Cons.

Ownership model of Rust addresses both of these. Only difficulty is that it takes a while to grasp the concepts, which is manageable.

MethodologyProsCons
Low-level controlControl over memory, Fast runtimeError prone, Skill issues
Garbage CollectorNo errors, but no control over memorySlow runtime
OwnershipControl over memory, Mostly error free, Fast runtimeSkill issues

Ownership Rules

Now there are some ownership rules we should keep in mind while learning.

  1. Each value must have an owner

  2. Values can have only one owner

  3. When owner gets out of scope, the value is dropped

Let's try to understand, with a simple example.

fn main() {
    let x = String::from("Hello World");

    println!("{}", x); // Hello World
}

(this is how we use strings in Rust, we will discuss more of this later)

We have assigned the value "Hello World" to x

Hence x can be considered as the owner of the value "Hello World"

fn main() {
    let x = String::from("Hello World");

    let y = x; // Err

    println!("{}", x);
}

Now it may sound strange if you compare with other Programming languages, but in Rust the line y = x will throw a compilation error.

error[E0382]: borrow of moved value: `x`
 --> src/main.rs:6:20
  |
2 |     let x = String::from("Hello World");
  |         - move occurs because `x` has type `String`, which does not implement the `Copy` trait
3 |
4 |     let y = x; // Err
  |             - value moved here
5 |
6 |     println!("{}", x); // Hello World
  |                    ^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
4 |     let y = x.clone(); // Err
  |              ++++++++

For more information about this error, try `rustc --explain E0382`.
warning: `ownership_rules` (bin "ownership_rules") generated 1 warning
error: could not compile `ownership_rules` (bin "ownership_rules") due to previous error; 1 warning emitted

Don't be confused! Let me explain what it means in simpler terms.

y = x in Rust means that the ownership of the value "Hello World" will be transferred from x to y. But that's a problem because we're printing x in the next line, and x holds no value.

On the other hand, let's assume both x and y holds the value "Hello World" and that violates the 2nd rule of Ownership, "Hello World" cannot have two owners.

fn main() {
    let x = String::from("Hello World");

    let y = x; // Err

    println!("{}", y); // Hello World
}

This will work.

fn main() {
    let x = String::from("Hello World");

    let y = x.clone();

    println!("{}", x); // Hello World
    println!("{}", y); // Hello World
}

This will also work.

.clone() method creates another entity of the value "Hello World" stored in x

One thing to note is the example shown above for string is not valid for integers

fn main() {
    let x = 5;

    let y = x;

    println!("{}", x); // 5
    println!("{}", y); // 5
}

.clone() method is not required here. We won't get any errors.

This is because integers are stored in Stack, while strings are stored in Heap, only pointer to string is stored in Stack. In brief.

(we will discuss more of this while discussing about Stack and Heap)

This is a simple example to explain the 3rd rule of Ownership.

fn main() {
    {
        let x = 5;
        println!("{x}"); // 5
    }
    println!("{x}"); // Err
}

x goes out of scope after the {} and it's value is dropped throwing an error.

error[E0425]: cannot find value `x` in this scope
 --> src/main.rs:6:16
  |
6 |     println!("{x}"); // Err
  |                ^ not found in this scope

For more information about this error, try `rustc --explain E0425`.
error: could not compile `ownership_rules` (bin "ownership_rules") due to previous error

Conclusion

That's all I can say about Ownership in Rust in brief. Many more things of high importance are still left to be discussed. We'll discuss and digest all of them with time. Cheers!