T rust learning -- the basic concept of ownership

Ownership is the most distinctive feature of trust, which enables trust to ensure memory security without garbage collection mechanism. So what is ownership and what is its use?

First, ownership in t rust means:
1. Ownership rules:

(1),Rust Each value in has a variable called its owner.
(2),You can only be one owner at a time.
(3),When the owner is out of scope, the value will be deleted.

2. What is scope: scope is similar to: {let s = "hello";}

3. Memory and allocation:
In t rust, when the variable with memory exceeds the range, the memory will be released automatically, for example:

fn main() {
        // From now on, s is valid
        let s = String::from("hello"); 

        //Do some operations with s
    // This scope is now closed and s is no longer valid

**: when the variable is out of range, rust calls a special function for us. This function, called drop, is where the creator of a String (as illustrated in the above example) can place code to return to memory. Rust will automatically close the division call in the closing brace.

*How variables and data interact: moving
Multiple variables can interact with the same data in different ways in Rust. For example:

fn main() {
    let x = 5;
    let y = x;
//"Bind value 5 to x; then copy the value in x and bind it to y." Now, we have two variables x and y, both of which are equal to 5. This is true because integers are simple values with a known fixed size, and these five values are pushed onto the stack.

Let's take a look at the String type:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
//This looks very similar to the previous code, so we can assume that it works the same way: that is, the second line copies the value in s1 and binds it to s2. But that's not exactly what happens.

// A string consists of three parts: a pointer to memory, which is used to save the content, length and capacity of the string. This set of data is stored on the stack. 

//Length is the amount of memory (in bytes) that String is currently using. Capacity is the total amount of memory (in bytes) received by String from the allocator. The difference between length and capacity is important, but it is not important in this case, so the capacity can be ignored for the time being.

//When we assign s1 to s2, the String data will be copied, which means that we will copy the pointer, length and capacity on the stack. We do not copy data to the heap referenced by the pointer.

//If Rust replicates the heap data instead, the runtime performance s2 = s1 can be very expensive if the data on the heap is large.

//When the variable is out of range, Rust automatically calls the drop function and clears the heap memory of the variable. However, if there are two data pointers pointing to the same location. This is a problem: when s2 and s1 are out of range, they will both try to free the same memory. This is called a double release error and is one of the memory security errors. Releasing memory twice may lead to memory corruption, which may lead to security vulnerabilities.

Ensure data security:
To ensure memory security, details of this situation appear in rust. Rust does not attempt to copy the allocated memory, but thinks that s1 is no longer valid. Therefore, when s1 is out of range, rust does not need to release anything. Check what happens when you try to use s1 after creating s2; It doesn't work:

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

    println!("{}, world!", s1);
//let s1 = String::from("hello"); 
               | -- `move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait`
 //`let s2 = s1;`
          `-- value moved here`
 //` println!("{}, world!", s1);`
                           `^^ value borrowed here after move`

There are concepts of shallow copy and deep copy in js. But since rust also invalidates the first variable, rather than being called a shallow copy, it is called a move. In this example, we say that s1 has moved into s2. This is the concept of mobile in trust

This explains the above problems! Only s2 is valid. When it is out of range, it alone can free memory, and we're done.

In addition, this implies a design option: Rust will never automatically create a "deep" copy of the data. Therefore, any automatic replication can be considered cheap in terms of runtime performance.

How variables interact with data: cloning
If we really want to deeply copy the heap data of String, not just the stack data, we can use a general method called clone, for example:

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

    println!("s1 = {}, s2 = {}", s1, s2);
//At this time, s1 still works. Its value is just copied, but the storage space it points to is not copied.

Stack data only: copying
Let's start with an example:

fn main() {
    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);
//What this code reveals is:
//Types such as integers of known size at compile time are completely stored on the stack, so you can quickly make copies of actual values. This means that we have no reason to prevent x from taking effect after creating the variable y. In other words, there is no difference between deep replication and shallow replication, so calling cloning is no different from ordinary shallow replication. We can omit the cloning operation.

Rust has a special annotation called the Copy feature, which we can place on types such as integers stored on the stack. If the type has the "Copy" feature, older variables can still be used after allocation. If the type or any part of it implements the Drop attribute, rust will not allow us to annotate the type with the Copy attribute. An error occurs when the annotation type is outside the scope of the special type and needs to be processed.

So what is the type of replication? As a general rule, any set of simple scalar values can be Copy, and anything that does not require allocation or some form of resource can be Copy. Some of the replication types are:

* All integer types, for example u32. 
* Boolean Boolean value, the value is true and false. 
* All floating point types, for example f64.   
* Character type,`char`.   
* Tuples (if they contain only) Copy Type of). For example,`(i32, i32)`yes Copy,however`(i32,String)`no

Ownership and functions:
The semantics used to pass values to functions are similar to those used to assign values to variables. Just like assignment, passing a variable to a function will move or copy. The following is an example with comments that show where variables enter and go out of scope:

fn main() {
    let s = String::from("hello");  // s into scope

    takes_ownership(s);             // The value of s is moved into the function
                                    // ... Therefore, it is no longer valid here and after

    let x = 5;                      // x into scope

    makes_copy(x);                  // x will move into the function,
                                    // But i32 is Copy, so you can also use x after that

} // Here, x is out of scope, followed by s. But because the value of s was moved, nothing special happened.

fn takes_ownership(some_string: String) { // some_string into scope
    println!("{}", some_string);
} // Here, some_string out of scope and call 'drop'. Backup memory freed.

fn makes_copy(some_integer: i32) { // some_integer into scope
    println!("{}", some_integer);
} // Here, some_integer is out of scope. Nothing special happened.

Return value and scope

The return value can also transfer ownership, for example:

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return value to s1

    let s2 = String::from("hello");     // s2 enter scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into takes_and_gives_back, its return value is also moved into s3
} // Here, s3 is out of scope and memory is freed. s2 was out of scope but moved, so nothing happened. s1 is out of scope and memory is freed.

fn gives_ownership() -> String {             // gets_ownership moves its return value to the function that calls it

    let some_string = String::from("hello"); // some_string into scope

    some_string                              // some_string is returned and moved to call gives_ In the function of ownership

// take_and_gives_back will get a String and return one
fn takes_and_gives_back(a_string: String) -> String { // a_string into scope
    a_string  // a_string is returned and moved to call takes_ and_ gives_ In the function of back

The ownership of a variable follows the same pattern every time: assign a value to another variable and move it. When a variable containing data on the heap is out of scope, the value is deleted unless the data has been handed over to another variable.

Having ownership and then returning ownership of all features is a bit tedious. What if we want functions to use values instead of ownership? It's very annoying that in addition to any data generated by the body of the function we may also want to return, we need to pass it back if we want to use them again.

You can use tuples to return multiple values, for example:

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

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a string

    (s, length)

This is a common concept and should be too much work. Fortunately for us, Rust has the function of this concept, which is called reference.

Tags: Rust

Posted by jamfrag on Mon, 23 May 2022 19:53:47 +0300