1

Original link: Understanding the Rust

The day for the first taste of Rust has finally arrived. You write down a few lines of Rust , and then enter the command cargo run on the command line and wait for the compilation to pass. Rust heard before that 061ab3c318c4dd is a language that can run as long as the compilation can pass. You are excitedly waiting for the program to run normally. The compilation ran, and then immediately output an error:

error[E0382]: borrow of moved value

It seems that you have encountered a "borrow checker" problem.

What is a borrow checker?

Rust borrow checker is the reason for one of the cornerstones of Rust, it can help (or forced) you manage the "ownership", that official chapter document described ownership : "Ownership is the most special feature of Rust, It ensures that Rust does not require a garbage collection mechanism to ensure memory safety."

Ownership, borrowing checker, and garbage collection: These concepts can be talked about a lot. This article will introduce what the borrowing checker can do for us (what can prevent us from), and the difference between it and other memory management mechanisms.

This article assumes that you have a certain understanding of high-level languages, such as Python, JavaScript or C#, and does not require knowledge of computer memory working principles.

Garbage Collection vs. Manual Memory Allocation vs. Borrowing Check

For many commonly used programming languages, you don't need to consider where the variables exist, just declare the variables directly, and the rest of the language's runtime environment will be processed by garbage collection. This mechanism abstracts computer memory management, making programming easier and more unified.

But this requires us to go deeper to show the difference between it and borrow check. heap start with stack and heap 061ab3c318c574.

Stack and heap

Our program has two kinds of memory to store values, stack stack and heap heap . There are some differences between them, but we only need to care about the most important point: the data stored on the stack must be fixed-size data, which is easy to access and has low overhead; like strings (variable length), lists and other possessions Variable size collection type data, stored on the heap. Therefore, the computer needs to allocate a large enough heap memory space for these uncertain data, this process will consume more time, and the program accesses them through pointers, rather than direct access like the stack.

In summary, the stack memory accesses data quickly, but requires a fixed data size; although the heap memory access speed is slower, the data requirements are loose.

Garbage collection

In languages with a garbage collection mechanism, the data on the stack will be deleted when it exceeds the scope of the scope, and the data on the heap will be processed by the garbage collector after it is no longer used, and the programmer does not need to pay attention to what happens on the stack. matter.

But for C , the memory must be managed manually. Those lists that can be simply initialized in a higher-level language need to manually allocate heap memory to initialize in C language, and the data needs to be manually released when the data is not used, otherwise it will cause a memory leak, and the memory is only Can be released once.

This process of manually allocating and manually releasing memory is prone to problems. Microsoft confirmed that 70% of their vulnerabilities are caused by memory-related issues. Since the risk of manually manipulating the memory is so high, why use it? Because it has higher control and performance than the garbage collection mechanism, the program does not need to stop and spend time checking which memory needs to be released.

Rust lies between the two. By recording the use of data in the program and following certain rules, the borrowing checker can determine when the data can be initialized and when it can be released (release is called drop in Rust), combining the convenience of garbage collection and manual The performance of management is like a memory manager embedded in the language.

In practice, we can perform three operations on the data under the ownership mechanism:

  1. Transfer ownership of data directly
  2. Copy a copy of the data, and transfer the ownership of the copied data separately
  3. Transfer the reference of the data, retain the ownership of the data itself, and let the receiver temporarily "borrow" ( borrow )

Which method to use depends on the scene.

Borrow other capabilities of the checker: concurrency

In addition to dealing with memory allocation and release, the borrow checker can also prevent data competition, just as Rust calls "fearless concurrency", allowing you to perform concurrent and parallel programming without any worries.

shortcoming

Good things always come with a price, and Rust's ownership system also has flaws. In fact, if it were not for these flaws, I would not write this article specifically.

It is difficult to get started, which is a major disadvantage of the borrowing check mechanism. There are many newcomers in the Rust community who have been tortured by it, and I myself have spent a lot of time mastering it.

For example, under the borrowing mechanism, sharing data will become more cumbersome, especially when sharing data while changing the data scenario. Many data structures that can be easily created in other languages are more troublesome in Rust.

But when you understand it, writing Rust code will be easier. I really like a sentence in the community:

The rules of the borrowing mechanism are like spell checking. If you don't understand them at all, then the code you write is basically wrong. Only when you understand them calmly will you write the correct code.

A few basic rules:

  1. Whenever a parameter variable (non-variable reference) is passed to a method, the ownership of the variable is transferred to the calling method, after which you can no longer use it.
  2. Whenever you pass a reference to a variable (so-called borrowing), you can pass any number of immutable references, or one mutable reference. In other words, there can only be one variable reference.

practice

After understanding the borrowing check mechanism, let's put it into practice now. We will use variable length in Rust List: Vec<T> type (similar in Python list and JavaScript in Array ), characteristics of the variable length determines it requires the use of heap memory to store.

This example is deliberate, but it can illustrate the above rules very well. We will create a vector , pass it as a parameter to a function to call, and then see what happens in it.

Note: The following code example will not compile

fn hold_my_vec<T>(_: Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7, 11, 13, 17];
    hold_my_vec(v);
    let element = v.get(3);
    
    println!("I got this element from the vector: {:?}", element);
}

After running, you will get the following error:

error[E0382]: borrow of moved value: `v`
--> src/main.rs:6:19
          |
        4 |     let v = vec![2, 3, 5, 7, 11, 13, 17];
          |         - move occurs because `v` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
        5 |     hold_my_vec(v);
          |                 - value moved here
        6 |     let element = v.get(3);
          |                   ^ value borrowed here after move

This error message tells us that Vec<i32> does not implement the Copy feature ( trait ), so its ownership has been transferred (borrowed), and its value cannot be accessed after this. Only the types that can be stored on the stack implement the Copy feature, and the Vec type must be allocated on the heap memory, it cannot achieve this feature. We need to find another means to deal with similar situations.

Clone

Although the Vec type variable cannot achieve the Copy feature, it does achieve the Clone feature. In Rust, cloning is another way of copying data. Unlike copy, which can only copy data on the stack and has low overhead, cloning can also be oriented to heap data, and the overhead can be high.

Going back to the above example, the scenario where the value is passed to the function, then we can also give it a vector clone. The following code can run normally:

fn hold_my_vec<T>(_: Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7, 11, 13, 17];
    hold_my_vec(v.clone());
    let element = v.get(3);

    println!("I got this element from the vector: {:?}", element);
}

But this code actually did a lot of useless work. The hold_my_vec function didn't use the passed vector, just received its ownership. And the vector in the example is very small, there is no burden to clone, for the first stage of contact with rust development, so you can easily see the results. In fact, there is a better way, which will be introduced below.

Quote

In addition to directly transferring the ownership of the value of the variable to the function, you can also "borrow" it. We need to modify the next hold_my_vec function signature, it received parameters from Vec<T> change &Vec<T> , that is a reference type. The method of calling this function also needs to be modified to let the Rust compiler know that it is just a reference to the vector — a borrowed value, and hand it over to the function for use. In this way, the function will temporarily borrow this value, and it can still be used in subsequent code.

fn hold_my_vec<T>(_: &Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7, 11, 13, 17];
    hold_my_vec(&v);
    let element = v.get(3);

    println!("I got this element from the vector: {:?}", element);
}

Summarize

This article is just a brief overview of the borrowing check mechanism, what it does and why it does it. More details are left to the readers to dig for themselves.

In fact, as your program code expands, you will encounter more difficult problems, and you need to think more deeply about ownership and borrowing mechanisms. Even in order to fit Rust's borrowing mechanism, you have to redesign the code organization structure.

Rust's learning curve is indeed steep, but as long as you continue to learn, you can always move up.


明酱
62 声望6 粉丝