This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

In the previous article, the distribution of memory, and made a simple introduction of Rust, when leave variable scope, the Rust will automatically call drop function to recovery of memory, looks simple principle, but in more complex scenarios of the behavior of the code may be unpredictable, such as when there are multiple variables when using the memory allocated on the heap. Now let’s explore some of these scenarios.

How variables interact with data – movement

Multiple variables in Rust can interact with the same data in a unique way, as shown in the following code, assigning the value of variable X to y:

fn main() {
    let x = 1;
    let y = x;
}
Copy the code

We can probably deduce the principle of the above code: binding the integer 1 to the x variable, let y = x creates a copy of x and binds that copy to y. Now we have two variables, x and y, both equal to 1. And that’s what actually happens, because integers have simple values of known fixed sizes, so these two ones are put on the stack.

The simple example above is given a fixed size. Now let’s look at the complex example which is String.

fn main() {
    let str1 = String::from("hello");
    let str2 = str1;
}
Copy the code

The code above looks very similar to the integer example, so we might assume that they behave similarly: that is, the second line might generate a copy of STR1 and bind to STR2. Well, that’s not exactly the case.

First we need to know what the bottom of a String looks like. In memory, a String consists of three parts, as shown below. This is the in-memory representation of the String that binds the value hello to STR1. A pointer to the memory that holds the contents of the string, a length, and a capacity. This set of data is stored on the stack. On the right is the memory portion of the heap where the contents are stored.

The length indicates how many bytes of memory are currently used for the contents of the String. Capacity is the total number of bytes of memory that String retrieves from the operating system. The difference between length and capacity is important, but not in the current context, so you can ignore capacity for now.

When we assign str1 to str2, String’s data is copied, which means we copy its pointer, length, and capacity off the stack. We are not copying the heap to which the pointer points. In other words, the data in memory is represented as shown below.

This representation does not look like the one shown below, which is what memory would look like if Rust also copied data on the heap. If Rust does this, operating str2 = str1 can have a significant impact on runtime performance when the heap is large.

We mentioned earlier that Rust automatically calls the drop function and cleans up the heap memory for variables when they leave scope. However, Figure 2 shows that two data Pointers point to the same location. Here’s the problem: when STR2 and STR1 go out of scope, they both try to free the same memory. This is an error called double free, and one of the memory security bugs mentioned earlier. Freeing (the same) memory twice causes memory contamination, which can lead to potential security vulnerabilities.

To ensure memory security, Rust has another unique handle in this scenario. Rather than trying to copy the allocated memory, Rust thinks STR1 is no longer valid, so Rust doesn’t need to clean anything up after STR1 leaves scope. See what happens when you try str1 after STR2 has been created:

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

	println!("{}, world!", s1);
}
Copy the code

Cargo Run returns an error because Rust prohibits using invalid references:

error[E0382]: use of moved value: `s1` --> src/main.rs:5:28 | 3 | let s2 = s1; | -- value moved here 4 | 5 | println! ("{}, world!" , s1); | ^^ value used here after move | = note: move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` traitCopy the code

If you’ve heard the terms shallow copy and deep copy in other languages, copying Pointers, length, and capacity without copying data might sound like shallow copy. But because Rust also invalidates the first variable, this operation is called a move, not a shallow copy. The above example can be read as s1 being moved into S2. So what happens is shown below.

This solves the quadratic release error, because only S2 is valid, and when it leaves scope, it frees its own memory, end.

There is also an implicit design choice: Rust will never automatically create a “deep copy” of data. Therefore, any automatic replication can be considered to have little impact on runtime performance.

conclusion

The article was first published in the wechat public account Program Yuan Xiaozhuang, at the same time in nuggets.

The code word is not easy, reprint please explain the source, pass by the little friends of the lovely little finger point like and then go (╹▽╹)