The ownership of

The core feature of Rust is that all programs manage how they use the computer’s memory at run time. Some languages have garbage collection mechanisms that constantly seek out unused memory at run time (such as JS), and some languages require programmers to explicitly allocate and free memory (such as C and C ++). Rust takes a third approach:

  • Memory is managed through an ownership system that contains a set of rules that the compiler checks at compile time
  • The ownership feature does not slow down programs when they run because Rust brings them all up to compile time

Ownership solves the problem

Prerequisite: Stack and Heap

In a system-level programming language like Rust, whether a value is on the stack or on the heap has a significant impact on the behavior of the language and the decisions you make.

Stack: Stack storage in which all data stored must have a known fixed size.

Heap: Heap memory. All data of unknown size or that may change size at run time must be stored on the Heap. When you place data in the Heap, the operating system finds a large enough space in the Heap, marks it as usable, and returns a pointer.

Problem solved

  • Track which parts of the code are using which heap data
  • Minimize the amount of duplicate data on the heap
  • Clean up unused data on the heap

Rules of ownership

  • Each value has a variable that is the owner of the value
  • Each value can have only one owner at a time
  • This value is removed when the owner exceeds the scope.

A variable is the owner of the value

Try using a non-scalar type — String (where String and String are not the same concept, similar to the basic wrapper types in JS) : First create a String from a String literal using the String::from function. Example:

fn main() {
    let mut s = String::from("Hello");
    
    s.push_str(", World");

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

Why can String values be modified, but not String literals?

Because string literals are known at compile time, their text content is hardcoded directly into the final executable. So it’s fast and efficient, but it’s immutable. Recycling is also a simple off-stack.

String, on the other hand, in order to support its variability, allocates memory on the heap to hold text of unknown size at compile time, so it requests memory at run time by calling String::from, When you run out of String, you need to use some sort of analog to return memory to the operating system (js CG, c++ manual). Here’s how Rust works: When a variable with this value goes out of scope, memory is automatically returned to the operating system immediately by calling the drop function for the current variable. To be clear, Rust values have an owner, the corresponding variable, which automatically triggers the drop when out of scope.

Each value can have only one owner at a time

So here’s the problem, let’s modify the above code as follows:

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

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

So rust compiles: error[E0382]: Borrow of Moved Value: s1, which is the second rule of ownership: each value can have only one owner at a time. Let’s assume that we have two owners, so that Pointers to s2 and s1 point to the same block of memory (js currently does this).

So when both variables go out of scope, they both try to free the same block of memory, which is a classic double free problem and can cause some data in use to be corrupted. Rust solves this problem in a very straightforward way, leaving S1 uninitialized and S2 bound to the heap memory data, transferring ownership of the values from S1 to S2.

This process is called Move in Rust and is essentially a shallow copy (like JS) + invalidation of the original variable.

That’s the rule of ownership and why it’s set up that way, and the core is to standardize operations and improve security.

Extension – how variables and data interact

  • clone

Now that we know that assignment in head is a move process, that is, a shallow copy, Rust provides a function: clone when we need to use a deep copy

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

    println!("{} {}",s1,s2);
}
Copy the code
  • copy trait

Integers, a type stored entirely on Strack (analogous to primitive data types), itself has an implementation of the Copy trait. If a type or part of that type implements a DROP trait, Rust does not allow it to implement a copy trait. The copy trait simply copies data on the stack. Let s1 = 1; let s1 = 1; let s2 = s1.clone(); The result is that s1’s value is copied to S2, but if it is a value on the heap that implements the Drop trait, it is not allowed to implement a copy trait, only move.

Ownership and function

  • Passing a value to a function parameter is similar to assigning a value to a variable, that is, it is either moved or copied. Example:
fn main() {let s = String::from("Hello World");

    take_ownership(s); // The value of s will be moved to the take_ownership function. After this line, s will be invalid

    let x = 5;

    makes_copy(x); // The 5 is copied to makes_copy just as it is copied to another variable

    println!("{}",x);
}

fn take_ownership(some_string: String) {println!("{}",some_string);
}

fn makes_copy(some_number: i32) {println!("{}",some_number);
}
Copy the code
  • There is also a transfer of ownership when a function returns a value. Example:
fn main() {let s = gives_ownership(); 
    let s1 = String::from("hello1"); 
    let s2 = take_and_give_back(s1);
    println!("{}, {}",s,s2)
}

fn gives_ownership() - >String{
    String::from("hello")}fn take_and_give_back(a_string: String) - >String{
    a_string
}
Copy the code

reference

There is a problem here. The property of my value in main moves to another function, but I still want to use the value after main. What do I do? Let a function use a value without taking ownership of it.

fn main() {let s = String::from("hello"); 
    let len = get_length(&s);
    println!("{}, {}",s,len)
}

fn get_length(s:&String) - >usize{
   s.len()
}
Copy the code

Where the argument is of type &String, the & symbol allows you to reference some value without taking ownership of it. As shown, s is a reference to S1.

If a move exists, it does not need to drop. If a move exists, it does not need to drop. If a move exists, it needs to drop.

Mutable and immutable references

Immutable reference

The read ability of the corresponding ownership is borrowed, and the corresponding value is not allowed to be modified. As in the above example, the value of S cannot be modified.

The variable reference

Modify the above example by borrowing the read/write capabilities of the corresponding ownership to allow modification of the corresponding values:

fn main() {let mut s = String::from("hello"); 
    let len = get_length(&mut s);
    println!("{}, {}",s,len)
}

fn get_length(s:&mut String) - >usize{
    s.push_str(", world!");
    s.len()
}
Copy the code
  • There can be only one mutable reference to a piece of data in a particular scope. Example:
fn main() {let mut s = String::from("hello"); 
    let s1 = &mut s;
    let s2 = &mut s; // An error will be generated
    println!("{}, {}",s1,s2)
}
Copy the code
  • You cannot have both a mutable reference and an immutable reference
  • You can have multiple immutable references

Dangling reference problem

A dangling pointer prematurely frees the memory to which the pointer is pointing, that is, the heap has been freed and the pointer is still pointing to the heap. In Rust, the compiler can guarantee that no dangling references are generated: if a reference points to data, the compiler ensures that the reference points to data that is not out of scope, for example:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() - > &String { Error [E0106]: Missing Lifetime specifier
    let s = String::from("hello"); 
    &s
} // s out of scope, the memory of the string is freed
Copy the code

Slice type

Slice allows you to refer to a contiguous sequence of elements in a collection without referring to the entire collection. Slice is a class A reference, so he has no ownership.

Exercise: Enter a string and return the first word found in the string.

fn main() {
    let mut s = String::from("hello world");
    let index = first_word(&s);

    s.clear(); // Modify the original s
    println!("{}",index)
}

fn first_word(s: &String) - >usize {
    let bytes = s.as_bytes();

    for (i,&elem) in bytes.iter().enumerate() {
        if elem == b' '{
            return i;
        }
    }
    s.len()
}
Copy the code

We used the first_word function to obtain the end index of the first word, but we modified the original variable S after obtaining the changed index. At this time, we have saved the result 5 to the index variable, but the index is not synchronized when s changes. This is where the problem arises. When our index needs to synchronize s, it is certainly not “safe” for Rust to keep triggering artificially. So Rust provides a solution to this problem: the slice type.

String slice

The String silce is a reference to part of a String, as in:

fn main() {
    let s = String::from("hello world");
    
    let hello = &s[0.5];
    let world = &s[6.11];
    println!("hello is {},world is {}",hello,world)  // hello is hello,world is world
}

Copy the code

In this case, unlike the reference concept we learned above, the hello variable is a pointer to s index 0, a slice of length 5. World is a pointer to s index 6 and a slice of length 5. As shown below:

Knowing the slice type, we override first_word to return a slice.

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);

    s.clear();   // The compiler reported an error
    println!("{}",word)
}
 
fn first_word(s: &String) - > &str {   // Return type "string slice", write &str
    let bytes = s.as_bytes();

    for (i,&elem) in bytes.iter().enumerate() {
        if elem == b' '{
            return &s[..i];
        }
    }
    &s[..]
}
Copy the code

Error [E0502]: Cannot borrow s as mutable because it is also borrowed as immutable

It is not possible to have a mutable reference and an immutable reference at the same time. Word is an immutable reference and clear is a mutable reference.

Other types of Slice

Arrays can also use slice as an example:

fn main() {
    let a = [1.2.3.4.5];
    let slice = &a[1.3];

    println!("{:? }",slice)
}
Copy the code

conclusion

The concepts of ownership, borrowing, and slice make Rust programs memory safe at compile time. The Rust language provides the same way to control memory as other system programming languages, but the ability for data owners to automatically erase their data when they leave scope eliminates the need to write and debug additional control code. The ownership system affects the way many other parts of Rust work.