How To Use Reference And Borrowing In Rust?

Introduction

Ownership and borrowing are unique and strong features of Rust programming language which helps to manage memory efficiently.

In the previous article, we learned about all the basic concepts of ownership in Rust, copy traits, and the variable's scope. Read the previous article for a basic understanding of How To Use Ownership In Rust? so that you can easily understand the concept of references and borrowing.

Understanding Reference in Rust

A reference is like a pointer in that it's an address we can follow to access the data stored at the address; some other variable owns that data. But, unlike a pointer, a reference will always point to a valid value of a particular type for the life of that reference.

In Rust, references are a way to refer to a value without passing the ownership of it. It's similar to passing a variable by reference in other languages like C, C++, etc. When we are passing a reference of a variable to another variable, we are creating a pointer that points to the original variable. Using this reference, we can access the value of that variable as we could use it in the ownership concept. Still, through reference, we cannot change the value of that variable as it is immutable by default.

Rust has two types of references: immutable (&) and mutable (&mut). In immutable references (&), we cannot change the variable's value but only read its value. In mutable references (&mut), we can not only read the variable's value but also modify its value.

References in Rust are always valid at the compile time, which means we can not create a reference to the variable that got dropped or does not exist.

Now let's see an example of the reference in Rust.

fn main()
{
    let x:String=String::from("Hello Rust");
    let length = calculate(&x);
    println!("The length of '{}' is {}.", x, length);
}
fn calculate(s1: &String) -> usize 
{
    s1.len()
} 

Output

output

In the above example, we have created a variable named x and assigned it a string. In the next line, we have created a variable length of integer type and in which we have assigned the return value of a function named calculate(). In the calculate() function, we pass the reference of string variable x, and in that function, it calculates the length and returns it to the calling function, and it gets assigned to the variable length, and then we print both x and length. In this example, we used the reference instead of the value. Here, we cannot modify x as it is an immutable reference.

Understanding Borrowing in Rust

Rust uses borrowing where the ownership is not completely transferred, but only its reference is transferred. As references are immutable and mutable, we can similarly let a variable be borrowed mutably and immutably.

Immutable borrowing in Rust

In immutable borrowing, we let a variable borrow a value only to read it but not to modify it. Let's understand this with an example.

fn main()
{
    let s1 = String::from("hello"); // s1 owns "hello"
    let s2 = &s1; // s2 references s1, borrows "hello"
    println!("{}", s1); 
    println!("{}", s2);
}

In the above example, we have created a variable named s1 and s2. In s1, we have assigned a string "hello"; in s2, we have assigned the immutable reference of  s1. Further, we have printed both s1 and s2. Here, s2 has borrowed the value of s1. As it is an immutable reference, s2 can only read s1 but can not modify it as s2 points to variable s1 and does not directly point to the value "hello" as s1 does. So, the value of s1 is borrowed by s2 as immutable.

Mutable borrowing in Rust

In mutable borrowing, we let a variable borrow a value for reading and modifying it. It can be done by adding &mut instead of &. Let's understand this with an example.

fn main() 
{
   let mut x = 5; // x must be of mutable type if we want to use its mutable borrowing
   increment(&mut x);
   println!("{}", x);
}
fn increment(i: &mut i64)
{
   *i=*i+1;
}

Output

Rust Image

In the above example, we have created a mutable variable named x and passed the mutable reference of x as an argument in the function named increment(). In the increment() function, we have assigned the mutable reference of x in the variable named i and we have incremented i by 1. In the next line of the main function, we have printed the value of x

Here, the value that is printed is 6, which means x is successfully modified by the increment() function. This is mutable borrowing.

Important points about borrowing

There are some essential points programmers need to keep in mind while using borrowing in their programs. They are

  • References must always be valid.
  • To make a mutable reference of a variable, the variable must be mutable.
  • At any given time, we can have either one mutable reference or any number of immutable references.

Conclusion

The borrow checker does the task of handling all the ownership and borrowing in Rust. The borrow checker checks all the references during the compilation so that the programmer does not need to keep track of all the references used in the program. It might be difficult to understand this concept initially, but as you get used to Rust and its syntax, you won't find it difficult to understand.

In this article, we have learned about references and borrowing in Rust and how to use them properly.

Frequently Asked Questions (FAQs)

Q. What is the difference between ownership and borrowing in Rust?

A. Ownership and borrowing are both key features of Rust's memory management system, but they work in different ways. Ownership refers to the idea that each value in Rust has a unique owner and that ownership can be transferred between variables or functions. Borrowing, on the other hand, allows a variable to reference a value owned by another variable, without taking ownership of it. Borrowed references can be either immutable (&) or mutable (&mut), depending on whether the borrowing variable needs to modify the value.

Q. How does Rust ensure that references are always valid?

A. One of the key advantages of Rust's ownership and borrowing system is that it helps prevent common errors like null pointer dereferences or use-after-free bugs. Rust's borrow checker checks all references at compile time, to ensure that they are always valid and do not cause memory safety issues. This means Rust code is much less likely to suffer from memory-related crashes or security vulnerabilities than code written in C or C++.

Q. What are some best practices for using ownership and borrowing in Rust?

A. Some tips for using ownership and borrowing effectively in Rust include: always using references instead of moving ownership when possible, preferring immutable references over mutable ones whenever possible to reduce the risk of data races, and avoiding creating too many levels of indirection with multiple borrowed references to the same value. It's also important to keep in mind Rust's borrowing rules, such as the fact that a value cannot have both mutable and immutable references at the same time and that mutable references are exclusive and cannot be shared with other references.


Similar Articles