avatarAnkit Tanna

Summary

The article discusses the use of references and borrowing in Rust to handle ownership and memory deallocation issues, avoiding the use of redundant variables and adhering to best practices.

Abstract

The Rust Programming Language article addresses the common "use after move" error by introducing the concepts of references and borrowing. Initially, the author presents a scenario where attempting to use a vector after it has been moved results in an error. The article then explores a workaround involving redundant variable creation and function return values, which is deemed unpleasant and against best practices. The preferred solution is to use references, which allow functions to access data without taking ownership, thus preventing the need for unnecessary copying or variable creation. The article explains that references provide read-only access to data, ensuring that the ownership and responsibility for memory deallocation remain with the original variable owner. This approach aligns with Rust's strict ownership and borrowing rules, which cannot be turned off, emphasizing the language's focus on memory safety and concurrency without sacrificing performance.

Opinions

  • The author suggests that the use of references and borrowing is a more elegant and efficient solution compared to creating redundant variables and returning them from functions.
  • The article conveys that following Rust's ownership and borrowing rules is essential for writing safe and efficient code, even though it may initially seem restrictive.
  • The author implies that Rust's borrow checker is a critical feature that should not be disabled, as it enforces memory safety and helps prevent common programming errors related to ownership and lifetimes.
  • There is an appreciation expressed for Rust's design choices, which prioritize read-only access rights when passing data between functions, thereby avoiding issues with memory management and deallocation.
  • The author encourages readers to engage with the content by providing feedback and suggests subscribing to a newsletter for further learning about Rust, indicating a commitment to community engagement and continuous learning within the Rust ecosystem.
The Rust Programming Language

The Rust Programming Language — References and Borrows — Understanding Values by Reference and Borrows

The pain of use after move errors finally ends!

In one of our previous examples we say an instance where use after move error was observed. Please have a look at the snippet which causes that error:

fn print_years(years: Vec<i32>) {
    for year in years.iter() {
        println!("{}", year);
    }
}

fn main() {
    let years = vec![2001, 2002, 2003, 2004]; // alloc()

    print_years(years); // transfer the ownership of years to print_years()
    print_years(years);
}

You must be aware that the variable years deallocation responsibility is with print_years(). So when the first time the method print_years() is called, years memory is marked as available. So when the method print_years() runs the second time, we get the use after move error.

We understood that this can be solved by making the print_years() return years and we create a new copy of the variable years2 as shown below:

fn print_years(years: Vec<i32>) {
    for year in years.iter() {
        println!("{}", year);
    }
    return years;
}

fn main() {
    let years = vec![2001, 2002, 2003, 2004]; // alloc()

    let years2 = print_years(years); // transfer the ownership of years to print_years()
    let years3 = print_years(years2);
}

We all agree that making such redundant variables is unpleasant and the names also do not follow the best practices. We can solve this problem by using References and Borrowing concepts. Have a look at the below code snippet:

fn print_years(years: &Vec<i32>) {
    for year in years.iter() {
        println!("{}", year);
    }
}

fn main() {
    let years = vec![2001, 2002, 2003, 2004]; // alloc()

    print_years(&years); // transfer the ownership of years to print_years()
    print_years(&years);
}

Observations:

  1. Notice a new character being used across the entire snippet &. This is known as a character to represent a Reference Type or a Reference Value.
  2. &Vec<i32> is a Reference Type and &years is a Reference Value.

This usage of & also solves the problem. Usage of & helps us avoid the cloning that we do and all those unwanted returns we make just to satisfy the compiler.

What’s going on with &?

  • &Vec<i32> is not a vector of i32 but it’s a reference of Vec<i32>.
  • With references we want to pass read-only access rights of the values that we pass around the functions.
  • When we do this, we dont allow the function accessing the reference values to manage the de-allocation of memory for the passed variable.
  • We mean to give the read-only access to the variable only temporarily and the ownership still to de-allocate the memory still resides with the parent function.
  • So when we want to find a length of the vector, we call .len() on it. The signature of the function is something like: fn len(&self) -> usize. So .len() does not get rights to de-allocate memory for the vector. They get only a read-only access.
  • What do we actually mean by passing &years — temporarily give access to print_years() for years but the ownership of de-allocation still stays with main() function. print_years() borrows years for doing something (read) with it but not everything (de-allocation).

We can’t turn a borrow checker off in Rust and we need to figure out someway to deal with it.

I hope you enjoyed this article about References and Borrowing in Rust. Please share your feedback in the comments section.

You can subscribe to my newsletter about The Rust Programming Language here. You can read about all the articles in this series here.

Rust
Webassembly
Programming
Web Development
Performance
Recommended from ReadMedium