Hi everyone. Before that, I wrote a post called Control Flow Statements in Rust.

Today we will see the ownership concept in Rust. Before I get started I have to say that it was really hard for me to understand.

Before starting, I’ll create a project with cargo;

cargo new understanding_ownership

cd understanding_ownership

Rust Programming Language

Introduction

Ummm, I don’t know how can I start. There are three concepts in Rust. These are ownership, borrowing, and lifetime. You may be familiar with them. Rust’s special feature is ownership.

Ownership Concept

Developers are responsible for freeing memory in other low-level programming languages. When you finished your job you should free that resource(memory) you used and you give it back. Let me give you an example in the C programming language.


char *some_str;

// We're allocating a space in memory
some_str = (char *) malloc(10);

// your logic here with some_str

// we're freeing some_str pointer and its resource
free(some_str);

What would be if we didn’t free it?

In short, memory leaking will occur. You don’t have to think about that in high-level programming languages. Because they probably will have a garbage collector mechanism. The garbage collector will do what it has to do.

How Rust Handles That?

Rust doesn’t have a garbage collector like C#’s garbage collector. Because it’s a low-level functional programming language. But there are some features called ownership, borrowing and lifetime. In this post, we’ll see the ownership concept.

Each variable is responsible for freeing resources. As we mentioned, when you’re writing code in C, you responsible for that. If you’re writing code in C# or Java, a garbage collector mechanism responsible for that.

A resource can have only one owner in Rust. However, some variables may don’t have a resource. For example references.

How Does This Mechanism work?

Let’s assume we create a variable like that;

fn main() {
  let a = 10;
}

When you assign the variable a to the another;

fn main() {
  let a = 10;
  let x = a;
}

Or when you passed the variable a to a function as a parameter;

fn main() {
  let a = 10;
  some_function(a);
}

You transfer ownership of the variable a. We’re calling this feature as move in Rust terminology. When you move a resource to a new one, the old one cannot be used anymore. Which means this avoids dangling pointer.

By the way, we should know that ownership only for heap-allocated types. For example;

// stack-allocated integer variable
let number = 30;

// we're copying value of 'number' variable to 'number2'. we're not moving it
let number2 = number;

println!("Number {} and Number2 {}", number, number2);

This will work because these are stack-allocated. Primitives like i32, u64, f64, or bool are small types. This means, copying them is fast and they don’t need to too much size in memory.

Now we’ll see an example for the heap-allocated variable. The ownership mechanism will work here.

let number = Box::new(30);
println!("Number {}", number);

let number2 = number;

This won’t work. It will throw an error like the use of moved. Why? As we mentioned before, variable ownership transferred to a function or a macro. In this case println! takes ownership of the number variable.

Let’s see with an example;

fn box_value(value: Box<u8>) {
  println!("New ownership is here {}", value);
}

fn main() {
  let a_variable = Box::new(30);

  box_value(a_variable);

  // won't work
  println!("Value is {}", a_variable);
}

Because a_variable doesn’t exist in this scope anymore. It doesn’t point to the address you know before. Because we moved the resource of a_variable.

What To Do Solve Ownership Problems?

1-) Create Scope Only Variables

You could create variable scopes using curly braces. The variable in the curly braces will work only inside of that scope.

fn box_value(value: Box<u8>) {
    println!("Value is {}", value);
}

fn main() {
    let a_variable = Box::new(30);

    {
        let a_variable = Box::new(30);

        box_value(a_variable);
    }

    println!("A variable {}", a_variable);
}

2-) Clone Variable

You could clone a heap-allocated variable. But it can be expensive.

fn main() {
    let a_variable = Box::new(30);

    println!("A variable {}", a_variable.clone());
    println!("A variable2 {}", a_variable);
}

3-) Returning Variables with Functions

You can transfer ownership of variables by returning values.

fn box_value(value: Box<u8>) -> Box<u8>{
    println!("Value is {}", value);

    value
}

fn main() {
    let a_variable = Box::new(30);

    let a_variable = box_value(a_variable);

    println!("A variable {}", a_variable);

    let a_variable = box_value(a_variable);

    println!("A variable2 {}", a_variable);
}

That’s all for now. If there is something wrong, please let me know. Because I still don’t understand clearly it.

Thanks for reading.

Resources