Memory Management in Rust: Ownership

Table of contents
Reading Time: 5 minutes

Do you know any programming language which allows memory management or you want to manage the memory on your own, maybe because you just don’t want to rely on the garbage collector, Rust allows us to do so? In this blog I am going to tell how you can actually manage your memory on an abstract level . Excited!!? Here we go…..

Ownership is the central feature of Rust programming language and it enables the memory management of the program, without using the garbage collector.
What makes Rust different from another programming language?
It is because Rust doesn’t use garbage collector for memory management.
Here, the programmer doesn’t need to manage the memory explicitly, rust takes the responsibility for managing memory.
And the explicit memory management led’s down the performance of the program. So, Rust keeps it in mind and make programs more efficient as compared to other programming languages.

There are three rules of Ownership:

  • There can only be one owner at a time.
  • Each value has a variable that’s called its owner.
  • When the owner goes out of scope, the value will be dropped.

Let’s understand the concept of ownership with the example.

1.fn main(){
2.let name :String = String::from("Pawan");
3.move_ownership(name);                   //ownership is moved here
4.println!("Hello {}",name);        //this will give error
5.}
6.fn move_ownership(new_name :String){
7.println!("Welcome {}",new_name);
8.}

If you try to execute the program, it’ll return an error, because ownership doesn’t allow this.

When line 3 executes, the ownership of the ‘name’ passed to the move_ownership(), and the name variable in the main function does not own ownership anymore.
That’s why it will give error.

If you run this code in other languages like Java or Python , they will create a copy of the variable and passed to the function, because they don’t have the concept of ownership.

Scope of the Variable :

The scope of the variable defines the lifetime of it, and the ownership of the value is valid with the scope only, until the function return or transfer ownership of the variable.

fn scope(){
//name is invalid here
let name :String =  String::from("Pawan");  //name is valid 

//rest of code

}//name is no longer valid, the scope is over

In the above example, the scope of the variable ‘name’ starts from where it is declared and till the closing curly braces. So, when the scope is over, the drop function will be called and deallocate the memory used by ‘name’.

Ways of interaction between variable:
Variables can interact with each other with the same data in different ways. Let’s understand with an example:

//example with integer type
fn main(){
let num_one :i32 = 50;
let num_two :i32 = num_one; //value of num_one is copied to it.
}

Let’s take this example in case of String:

//example with String
fn main(){
let name :String = String::from("Bisht");
let new_name :String = name; //new owner of the value "Bisht" is                                                   
                             new_name and name is no longer valid 
}

It looks like same as the previous example, but here the value is stored in the heap and string contains three parts which are: a pointer to the location, length of the string and capacity of the string they are stored in the stack and the value of the string is stored in the heap. Look in the figure mentioned below:

Above image shows the string pointing to the heap, where its data is stored but if the data is copied like in the previous example so, two strings will be pointing the same data in the heap, which will create a problem when both strings go out of scope, they both will try to free the same memory. So, this problem is known as double free error.
Freeing memory twice can lead to memory corruption.

In Rust, if the data deals with the heap, then it will not be copied, in this case, the ownership is moved to that variable which acquire the value of the variable.

Functions with Ownership:
In Rust, passing a variable to the function will move the owner or copy the value. Let’s understand with the example

fn main(){

let str :String = String::from(“Ownership moves”);

let date :i32 = 15;

copy_value(date);  //copies date

move_owner(str);  //moves the str,now str is no longer valid

println!("The date is {}",date);//prints date

println!("The string is {}",str);//returns error
}
fn move_owner(str2:String){
//some operations
}
fn copy_value(date_copy :i32){
//some operations
}

The above code shows that if we use the data which stored in the stack, will create a copy of it but the data which deals with the heap, will move the owner, so this happens because of ownership of the rust. Rust makes it possible because if it passes the copy of string, then the two strings will share same data which is present in the heap and again double free error comes into the picture which creates a race condition problem.

Return scope with Ownership:
Returning the scope means we can transfer the scope from one function to another. Let’s understand with the help of the example

fn main(){
let take :String= get_scope();     //catching the variable 
                                     with value and scope
}
fn get_scope()->String{
let str = String::from(“its ownership is transfered”);
str          //transfer its value and scope to the main function
println!("The string is {}",str);     //it will give error

}

The above code describes the function get_scope(), which transfer the scope of string ‘str’ from its scope to the main() function’s scope. If you try to execute the program, it will give an error because, before the println!() function the value and its scope are transferred to the main() function.

If you don’t want to move value or ownership to the function and also needs to use it further, then Rust provides a feature which will give this functionality to the developer called as Borrowing and References.

References means passing the reference or address of the variable into the function, and receiving the reference type variable as a function’s argument, means borrowing the value from the source. Let’s see the example of references and borrowing:

fn main(){

let text :String= String::from(“Example of reference and  
                  borrowing”);

borrow_with_reference(&text);       // reference of text passed

println!("The text is {}",text);  //value is used after passing to  
                                    the passing 
}

fn borrow_with_reference(ref_text :&String)  //borrowing the text
{
println!("The text is {}",ref_text);  // prints value of the           
                                         variable
}

Above program shows the borrow_with_reference() function borrows the variable ‘text’ by taking its reference and in the main() function, the reference of the variable is passed so that we can use that variable after passing to the function. Have a look at the diagram also:

Here the variable ‘s’ refer to the variable ‘s1’ and s1 points to the String stored in the heap.

Hope you all understand the concept of Ownership in Rust Programming Language.

References:

rust-lang.org

knoldus-advt-sticker


Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than two years in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute to the community which he attained from the community.