Reference and borrowing of Rust introduction series

1. Reference

In many scenarios, we may just want to read the value pointed to by a variable without obtaining its ownership. We can use references at this time. In fact, many other programming languages also have the concept of reference.

  • Simply put, a reference is to create a variable that points to the address of another pointer, rather than directly to the heap memory address that the pointer points to
  • Get a reference to a pointer variable through the & address character

For example, in Rust, we create references as follows:

let s1 = String::from("hello");

// Get the reference of s1
let s = &s1;
  • The following figure shows the reference relationship well

    • The variable s1 is a pointer address in the stack memory. The ptr records the address of the String("hello") stored in the heap memory
    • The variable s is also stored in the stack memory. ptr records the pointer address of s1 to realize the reference to String("hello")


2. Borrowing

The behavior of using references as function parameters is called borrowing. How to use borrowing to avoid the ownership of a variable moving can be seen in the following examples:

fn main() {
    let s = String::from("hello!");

    // Using the reference of s as the input parameter, the ownership of s will not move
    let len = get_length(&s);

    println!("the length of {} is {}", s, len); // the length of hello! is 6
}

fn get_length(string: &String) -> usize {
    // Like variables, references are immutable by default
    // string.push_str("world"); // `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

    string.len()
}

3. Variable reference

From the above example, we can know that references, like variables, are immutable by default. We cannot modify the value pointed to by references. But if you want to do this, you can use the mut keyword to make the reference variable:

fn main() {
    let mut s = String::from("hello!");

    /*
     * Use the mut keyword to take the variable reference of s as the input parameter,
     * In this way, the ownership of s will not be moved, and the value of S can be modified through variable reference in the function
     */
    let len = get_length(&mut s);
    
    // In get_ In the length function, we implemented the modification of s
    println!("the length of {} is {}", s, len); // the length of hello!world is 11
}

fn get_length(string: &mut String) -> usize {
    // Modify values through variable references
    string.push_str("world");
    string.len()
}

3.1 important limitations of variable references

  • Corresponding to a pointer variable, there can only be one variable reference in a specific scope. The reason is also well understood. If there are two variable references to a variable in a scope, it means that two variables may control the same memory space at the same time, and data competition will occur, which is easy to generate bug s at runtime. Therefore, Rust avoids such problems by checking at compile time

3.1.1 only one variable reference can exist in the same scope

As can be seen from the example, when we make two variable references to the variable origin, an error will be reported directly during compilation

fn main() {
    let mut origin = String::from("hello");

    let ref1 = &mut origin;
    let ref2 = &mut origin; // error: cannot borrow `origin` as mutable more than once at a time

    println!("{}, {}", ref1, ref2);
}

3.1.2 there can be multiple immutable references

If multiple immutable references are used at the same time, there will be no such restriction, because immutable references are actually read-only and there is no possible memory security risk. From this example, we can see that Rust is allowed to be used as follows:

fn main() {
    let origin = String::from("hello");

    let ref1 = &origin;
    let ref2 = &origin;

    println!("{}, {}", ref1, ref2);
}

3.1.3 in some scenarios, there can be multiple variable references

From the above two examples, we know that if there are more than two variable references in a scope at the same time, data competition may occur. Is there any scenario in which multiple variable references will appear? Let's look at the following example:

  • In fact, in the process of program execution, as long as you go out of the scope, the variables in the scope will be released. In this example, ref1 has been destroyed when ref2 is declared, so the first principle of variable reference can be guaranteed

    fn main() {
      let mut origin = String::from("hello");
    
      {
          let ref1 = &mut origin;
          
          println!("{}", ref1);
      }
    
      // When ref2 is declared, ref1 has been destroyed
      let ref2 = &mut origin;
    
      println!("{}", ref2);
    }

3.1.4 you cannot have both a mutable reference and an immutable reference

For a pointer variable, its variable reference and immutable reference are mutually exclusive and cannot exist at the same time. The reason is very simple. Variable references can modify the value pointing to the memory space. When the value is modified, the meaning of immutable references will not exist. Therefore, Rust will check during compilation. If it finds this situation, it will directly report an error:

fn main() {
    let mut origin = String::from("hello");

    let ref1 = &origin;
    let ref2 = &mut origin;// cannot borrow `origin` as mutable because it is also borrowed as immutable

    println!("{} {}", ref1, ref2);
}

4. Suspension reference

This bug scenario in C/C + + and other languages describes a scenario in which the memory space pointed to by a variable has been released or allocated to other programs, but this variable is still valid. In Rust, the compiler checks this scenario to avoid dangling references. Assuming the compilation passes, the following is a scenario that will generate dangling references:

fn main() {
    let s = String::from("haha");

    // The ownership of s is moved to the scope of the helper
    let a = helper(s);

    /*
     * Assuming the compilation passes:
     * When the helper is called, the memory space pointed to by s is released, but the reference of S is returned in the helper,
     * In fact, the reference is invalid, and the variable a becomes a dangling reference
     */
}

fn helper(s: String) -> &String {
    println!("{}", s);
    &s // error: missing lifetime specifier
}

But in fact, at compile time, Rust will not allow the helper to return the reference of s

Keywords: Front-end Rust

Added by soniared2002 on Fri, 04 Feb 2022 09:02:32 +0200