What is ownership
Rust contains an ownership system to manage memory At compile time, the ownership system checks a set of rules to ensure that the ownership feature allows your program to run without slowing down
To understand ownership, let's first look at Rust scope rules and mobile semantics
Scope rule
In Rust, like other languages, variables can be used in a defined scope In Rust, a scope is usually represented by {} The general scope includes the function body and if, else, match branches
Note: in Rust, "variable" is often referred to as "binding" Here, because "variables" are very invariant in Rust - they usually don't change because they don't change by default Instead, we usually think that the name is "bound" to the data, so it is named "binding" However, we use "variable" and "binding" alternately
We have a mascot variable, which is a string defined in the scope:
// `mascot` is not valid and cannot be used here, because it's not yet declared. { let mascot = String::from("ferris"); // `mascot` is valid from this point forward. // do stuff with `mascot`. } // this scope is now over, so `mascot` is no longer valid and cannot be used.
If we try to use mascot outside its scope, we will get the same error as the following example
{ let mascot = String::from("ferris"); } println!("{}", mascot);
error[E0425]: cannot find value `mascot` in this scope --> src/main.rs:5:20 | 5 | println!("{}", mascot); | ^^^^^^ not found in this scope
A variable is available from its declaration point until the end of the scope
Ownership and dropping
Rust adds a scope with different ideas Whenever an object goes out of scope, it will be "discarded" Dropping a variable means releasing any resources that are bound to it For file variables, the file is finally closed For variables to allocate memory and them, this memory will be released
In Rust binding, he has something "associated" with them, and they will release it. When the binding is lost, it means "owning" those things
In the above example, the mascot variable has a string associated with it String has its own heap allocated memory and string characteristics At the end of the scope, the mascot is "discarded", the string itself is discarded, and finally the string in memory is released
{ let mascot = String::from("ferris"); // mascot dropped here. The string data memory will be freed here. }
Mobile semantics
Sometimes we throw away at the end of the scope by associating a variable with something we don't want Instead, we want to transform the ownership of an object from one binding to another
The simplest example when declaring a new binding:
{ let mascot = String::from("ferris"); // transfer ownership of mascot to the variable ferris. let ferris = mascot; // ferris dropped here. The string data memory will be freed here. }
The key is to understand that once ownership is transferred, the old variables are no longer available In our previous example, after we transfer the ownership of String from mascot to ferris, we can no longer use mascot variables
In Rust, "transfer of ownership" is known as "movement" In other words, in the above example, the String value has been moved from mascot to ferris
If we use mascot, the compiler cannot compile the code after the String has been moved from mascot to ferris:
{ let mascot = String::from("ferris"); let ferris = mascot; println!("{}", mascot) // We'll try to use mascot after we've moved ownership of the string data from mascot to ferris. }
error[E0382]: borrow of moved value: `mascot` --> src/main.rs:4:20 | 2 | let mascot = String::from("ferris"); | ------ move occurs because `mascot` has type `String`, which does not implement the `Copy` trait 3 | let ferris = mascot; | ------ value moved here 4 | println!("{}", mascot); | ^^^^^^ value borrowed here after move
This is the famous "use after move" compilation error
Important: in Rust, only one can always have data for this period of time
Ownership in function
Let's look at an example of a string transferred to a function as an argument Pass something as an argument to the function and move it to the function
fn process(input: String) {} fn caller() { let s = String::from("Hello, world!"); process(s); // Ownership of the string in `s` moved into `process` process(s); // Error! ownership already moved. }
The compiler complains that the value s has moved
error[E0382]: use of moved value: `s` --> src/main.rs:6:13 | 4 | let s = String::from("Hello, world!"); | - move occurs because `s` has type `String`, which does not implement the `Copy` trait 5 | process(s); // Transfers ownership of `s` to `process` | - value moved here 6 | process(s); // Error! ownership already transferred. | ^ value used here after move
As you can see in the above code, first call process to change the ownership of the variable s The compiler tracks ownership, so an error occurs in the next call to process After the resource is moved, the previous owner is no longer available
This mode greatly affects the Rust coding method Its core is to ensure memory security, proposed by Rust
In other languages, the String value of the s variable can be implicitly copied before it is passed into our function But this operation will not happen in Rust
In Rust, ownership transfer (that is, move) is the default behavior
Copy instead of move
You may notice the compiler error message above, Copy trait is mentioned We haven't discussed trait at present, but the value implements Copy trait, which is copied instead of being moved
Let's look at the value that implements Copy trait u32 The following code reflects that we violated the above code, but the compiler did not have a problem
fn process(input: u32) {} fn caller() { let n = 1u32; process(n); // Ownership of the number in `n` copied into `process` process(n); // `n` can be used again because it wasn't moved, it was copied. }
Simple types are like numeric copy types They implemented Copytrait, which means that their copying is equivalent to moving The same action occurs for simple types Copying values is low consumption, so it makes sense for those values to be copied Copying string or vector or other complex types is very expensive, so they do not implement Copytrait and use mobile instead
The Copy type does not implement Copy
One way to handle errors, we see the above display by copying the type before it moves: using clone. In Rust A call Clone copies memory and generates new values The new value is moved, which means that the old value can still be used
fn process(s: String) {} fn main() { let s = String::from("Hello, world!"); process(s.clone()); // Passing another value, cloned from `s`. process(s); // s was never moved and so it can still be used. }
This method can be used, but it can slow down your code when clone is a full copy of data for each call This method usually involves memory allocation or other expensive operations We can avoid these overhead if we can "borrow" values by reference We learn to quote in the next unit
Learning borrowing
https://docs.microsoft.com/en-us/learn/modules/rust-memory-management/2-learn-about-borrowing