Understanding Reference and Borrowing in Rust for Better Coding

Midhun
Custom App Development
5 min readFeb 22, 2024
Reference and Borrowing in Rust

References in Rust are a type of pointer, which is an arrow to a value stored somewhere else in the memory. You can take the reference of a variable in Rust by the ‘&’ symbol. We use the dereference ‘*’, operator to get the value pointed by the reference. If you look at the assert_eq! statement below, you can see the use of the dereference(*) operator.

let name : String = String::new("David");

//taking a reference of the name String
let name_ref = &name;

let age:i32 = 34;

//taking the reference of an i32
let age_ref = &age;

assert_eq!(34,*age)

When you assign a value to a variable in Rust, that variable becomes the owner of the value. The owner is responsible for freeing up the memory held by the value when the owner goes out of scope. Borrowing allows you to temporarily get a reference of a variable without taking ownership.

Borrowing comes in two forms: mutable borrowing(&mut) and immutable borrowing(&). Borrow of a value ends when the reference goes out of scope, but the actual value borrowed is not dropped until all the references of that value are no longer in use, and the owner of that value goes out of scope. The point to note here is that there can only be one mutable reference of a value in Rust within a given scope. There could be multiple immutable references but when someone has taken a mutable reference then no one can take the immutable reference within the same scope.

//x is defined as mutable, i.e you can modify x
let mut x = 5;

//get a mutable reference of x in y
let y = &mut x;

*y = *y +1;

assert_eq!(6,x)

One thing to note is that the references variable is not responsible for dropping the value it points to, the memory of the value is dropped only after the owner of the value goes out of scope. This is an important point to understand borrowing in Rust.

The code snippet below will work fine because the mutable reference of ‘x’ taken in the inner scope by the variable ‘y’ goes out of scope after the closing curly braces of the outer scope which is at line 7. The immutable reference of ‘x’ taken by variable ‘z’ in the outer scope will work just fine because ‘y’ has already gone out of scope.

   let mut x = 5;
//outer scope
{
//inner scope
let y = &mut x;

*y = *y +1;
} //line 7

let z = &x;

println!("{}",z)

&str type:

It represents a reference to a slice of a string. It has two parts:

a) ‘&’: It indicates that it is a reference.

b) str: This is a string slice type in Rust. It is a fixed-size immutable view into a String data.

c) It is usually stored in the static memory of the program. You create a &str type by a string literal. &str can also point to the slice of string in heap memory or a stack-allocated string slice.

let name :&str = "David";

let job :String = String::from("driver");

let job_ref :&str = &job[..] //take a reference to slice of String

Deref Trait:

Deref trait allows you to customize the behavior of the dereference “*” operator. Deref trait is used by the compiler to implicitly dereference a type.

This is commonly used by smart pointers to behave like regular references.

pub trait Deref {

type Target:?Sized; //associated type and Target need not be sized
// so Target could be dynamic types

fn deref(&self)-> &Self::Target;
}

When you use the ‘*’ dereference operator on a type that implements the Deref trait, the Rust compiler will automatically call the ‘deref’ method from the Deref trait to get the inner value. When the compiler implicitly calls the ‘deref’ method over a type it is called Deref coercion. Deref coercion is the reason why we can pass a ‘&String’ reference to an owned string to a function that takes a ‘&str’ reference to a slice of string as the compiler implicitly converts the reference to an owned string to a reference of a slice of string(&str).

If T implements Deref<Target = U>:

  • Values of type &T are coerced to values of type &U
  • T implicitly implements all the (immutable) methods of the type U.

String type implements Deref<Target = str>

fn print(name: &str) {
println!("{}", name);
}
fn main() {
//owned string of type String
let first_name = String::from("David");
//reference to a slice of string of type &str
let last_name = "Goliath";

//both &String and &str can be passed to the print function which takes &str
//because compiler does Deref coercion for values of &String to &str
//since String implements Deref<Target = str>
print(&first_name);
print(last_name);
}

Always prefer to use borrowed types over borrowing the owned types. Like &str over &String, &[T] over &Vec<T> and &T over &Box<T>. As &String will introduce two indirections as the ‘String’ type itself has one indirection because the actual memory of the string data is on the heap and the String data structure is on the stack with a pointer to the memory on the heap, so if you take &String, the actual memory can only be retrieved after two indirections. Borrowed types directly reference the underlying data without introducing an additional layer of indirection, leading to more efficient code.

AsRef Trait:

AsRef trait is used in Rust for reference to reference conversion. If you want to convert from a source reference type to a destination reference type then you need to implement AsRef Trait on the source type reference. Let’s first look at the Trait definition then we can look at an example to make it more clear.

pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}

As shown above, you can implement the AsRef trait on any source type that returns a reference of type ‘T’ by implementing the ‘as_ref’ method on the source type reference.

Let’s look at an example to make it more clear.

//Animal struct which has two fields to store the features of an Animal
pub struct Animal {
pub no_of_legs: i32,
pub can_be_domesticated: bool,
}

//print_features...function which takes a type of T and T is any type which
//implements AsRef over the Animal type
fn print_features<T: AsRef<Animal>>(animal: T) {
println!("Number of legs: {} ", animal.as_ref().no_of_legs);
println!(
"Can be domesticated: {}",
animal.as_ref().can_be_domesticated
);
}
//Cat struct stores a reference to an Animal type
struct Cat<'a> {
animal: &'a Animal,
name: String,
}
//Cat struct implements AsRef over Animal type
//So any function that expects a Type that implements AsRef over Animal type
//we can pass a reference to Cat type
impl<'a> AsRef<Animal> for Cat<'a> {
fn as_ref(&self) -> &Animal {
self.animal
}
}

fn main() {
//create an animal
let animal = &Animal {
no_of_legs: 4,
can_be_domesticated: true,
};
//create a cat instance
let tom = Cat {
animal,
name: String::from("Cat"),
};
//call print_features which takes a reference of Animal
//but passing the reference to a Cat as Cat implements AsRef on Animal


print_features(&tom)
}

--

--

Midhun
Custom App Development

Midhun is a Senior Technical Manager at Zco. Loves programming in Rust and Golang. In free time works on Rust and GoLang projects for fun.