On references and borrowing of rust basic notes

Time:2021-12-6

Borrowing

Let’s start with a code:

fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    // do stuff with v1 and v2
    // hand back ownership, and the result of our function
    (v1, v2, 42)
}
let v1=vec![1, 2, 3];
let v2=vec![1, 2, 3];
let (v1, v2, answer) =foo(v1, v2);

If you have to write code like this, you won’t die.
Of course, this is not in line with rust’s habit. Maybe you can only write such code when you only understand the concept of ownership. There seems to be no other good way. Now let’s take a look at the concepts of references and borrowing.

What is References: just like the pointer of C language, V is referenced by a syntax similar to & V in rust, which means to refer to V’s resources instead of owning it (that is, the difference between borrowing and permanently owning it). I don’t know whether I have a problem with this, but the general meaning should be like this.

Here we should introduce the concept of borrow, let V2 = & V; V2 refers to V’s resource. V2 borrows V’s ownership of the resource. Since it is borrowed, you have to return it when it is used up. This is the same as the process of borrowing other people’s things in daily life. When V2 exceeds its scope, the system will not release the resource because it is borrowed (this is different from the concept of move. Move is almost this thing I gave you). After using it, we have to return the ownership to V, and then we can reuse v.

In the above example, V is immutable (immutable), so V2 that borrows the ownership of V is immutable (immutable). If you forcibly modify the value of V2, the following error will occur:

error: cannot borrow immutable borrowed content `*v` as mutable

Let’s rewrite the above code:

fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    // do stuff with v1 and v2

    // return the answer
    42
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let answer = foo(&v1, &v2);

// we can use v1 and v2 here!

In this code, & VEC < I32 > is used as the parameter type. Of course, we don’t need to care what type VEC < I32 > is now. We call all shapes like & T “reference”,
This means that it will not own resources, but borrow them. When the variables V1 and V2 exceed their scope, the system will return the corresponding resources to the source variable, that is, after we call foo (), we can reuse the original binding.

&Mut references

This is the second form of reference & mut T, while the previous one is immutable reference & T. variable reference allows you to modify your borrowed resources, like this:

let mut x = 5;
{
    let y =&mut x;
    *y += 1;
}
println!("{}", x);

This is an example in the official manual. What should be emphasized?
Some people are curious to say, isn’t y immutable? How can I change its value? It should be noted that y is indeed immutable. Immutability here means that y itself is immutable, that is, y can only point to X and cannot be modified into other references. Like this, y = &mut Z is not allowed, because y refers to x resources, X is variable, * y + = 1; It is equivalent to x + = 1; It’s variable here. Y points to x all the time.

Then why should the middle two sentences be enclosed by {}? In C language, {} is used to represent statement blocks, so as to limit the scope of variables. In rust, once you leave {}, y will exceed its scope. Also, y is the referenced x, which is equivalent to the resources borrowed by Y from X. now y is used up, Then the ownership must return to x, so the last output code can be called correctly. If {} is removed, the following error will be given:

error: cannot borrow `x` as immutable because it is also borrowed as mutable
    println!("{}", x);
                   ^
note: previous borrow of `x` occurs here; the mutable borrow prevents
subsequent moves, borrows, or modification of `x` until the borrow ends
        let y = &mut x;
                     ^
note: previous borrow ends here
fn main() {
}
^

I mean, didn’t you lend X’s resources to y? They haven’t used up (y not beyond their scope) and you don’t have anything. How can you use it?

Next, let’s talk about the rules of borrow

1. The scope of any row must be smaller than that of owner. Take the above example. If the scope of Y is smaller than that of X, what kind of error will occur?
The code is as follows:

let y;
let mut x = 5;
y = &mut x;
*y += 1;
//There's no output here. Y hasn't returned the ownership yet!

The error is as follows:

main.rs:4:14: 4:15 error: `x` does not live long enough
...... (the following is omitted)

It’s very clearX doesn’t live as long as y

2. This is very important. There can be 0 or n references of type & T, but there can only be one reference of type & mut t, if not, look at the following code:

let mut x = 5;
let y = &mut x;
let z = &mut x;
main.rs:4:18: 4:19 error: cannot borrow `x` as mutable more than once at a time

Think about it, or the example of borrowing things in life. How can you lend one thing to two or more people? If you just lend it to two or more people, it’s no problem. You can lend it to as many people (equivalent to & T), but if you want to lend it to two or more people to “use” &mut t together? It’s not necessary to fight. This is the “data race” data competition in the document. Here is the official definition, which is easy to understand:

There is a ‘data race’ when two or more pointers access the same memory location at the same time, where at least one of them is writing, and the operations are not synchronized.

There’s nothing wrong with the following code. It’s all for reference:

let mut x = 5;
let y = &x;
let z = &x;

Thinking about scope

Scope is a very important thing for borrow, which needs to be well understood.

For example, if you lend someone something, you must at least tell him when to return it. The scope is for this. Only in the specified scope can variables normally use resources. Once it exceeds the scope, that is, it no longer has the right to use the resource, the resource will naturally be returned to the original binding. This is the original sentence in the official document:

scope is the key to seeing how long a borrow lasts for.

This means that the scope determines how long a row can last. The document also gives two examples to explain the mistakes in the concept of row:

1. Iterator invalidation

let mut v=vec![1, 2, 3];
for i in &v {
    println!("{}", i);
}

This code can be executed normally, but the compiler will give a warning as follows:

main.rs:2:9: 2:14 warning: variable does not need to be mutable, #[warn(unused_mut)] on by default
main.rs:2     let mut v = vec![1, 2, 3];

It’s very sweet. It tells us that V doesn’t need to be modified. Paying attention to this problem may eliminate many potential problems.
Note that I here refers to V’s resources.

But there is a problem with the following code:

let mut v=vec![1, 2, 3];

for i in &v {
    println!("{}", i);
    v.push(4);
}

What’s wrong? Obviously, I borrow V’s resource and I is still in its scope, so V does not own the resource at this time, so this error is reported:

 error: cannot borrow `v` as mutable because it is also borrowed as immutable

2.use after free
This is an old question. There will be problems using the released resources, so in rust,A reference must live as long as the resource it refers to, rust will help you check this problem when compiling to prevent runtime errors.

For example, the following code:

let y: &i32;
{ 
    let x = 5;
    y = &x;
}
println!("{}", y);

The compiler gives this error:

error: `x` does not live long enough
    y = &x;
…………

X here obviously doesn’t live as long as y. The following code is the same:

let y: &i32;
let x = 5;
y = &x;
println!("{}", y);

The scope of Y starts from its declaration, that is, the first line. It is obvious that the scope of X is less than y. if this code is written in C language, it can be executed normally. The brilliance of rust is to help you check potential problems during mutation and prevent runtime errors.


Continuous update