[Translation] Understanding Ownership in Rust

The translator of this article is the front-end development engineer of 360 Qiwu Troupe

Original title: Understanding ownership in Rust

Original Author: Ukpai Ugochi

Original link: https://blog.logrocket.com/understanding-ownership-in-rust/


For the fifth year in a row, Rust is the most popular programming language in developer surveys conducted by Stack Overflow. Developers love Rust for many reasons, one of which is its memory safety guarantees.

Rust guarantees memory safety through a feature called ownership. Ownership works differently than garbage collectors in other languages ​​because it only consists of a set of rules that the compiler needs to check at compile time. The compiler will not compile if the ownership rules are not respected. The borrow checker ensures that your code follows ownership rules.

For languages ​​without a garbage collector, you need to explicitly allocate and free memory space. This can quickly become tedious and challenging when large codebases are involved.

Thankfully, memory management is handled by the Rust compiler using an ownership model. The Rust compiler will automatically insert a drop statement to free memory. It uses an ownership model to decide where to free memory; when the owner goes out of scope, the memory is freed.

fn main() {
    {
    let x = 5 ;
    // x 被丢弃在这里,因为它超出了范围
    }
}

What are stack and heap?

Both the stack and the heap are memory storage segments that your code can use at runtime. For most programming languages, developers are usually not concerned with memory allocations on the stack and heap. However, since Rust is a systems programming language, how values ​​are stored (on the stack or heap) is critical to how the language behaves.

How is memory stored on the stack? Suppose there is a stack of books on a table, and the books are arranged in such a way that the last book is at the top of the stack and the first book is at the bottom. Ideally, we wouldn't want the bottom book to slide out from under the stack, it would be easier to pick a book to read from above.

This is exactly how memory is stored on the stack; it uses a last-in, first-out approach. Here, it stores the values ​​in the order they were fetched, but deletes them in the reverse order. It's also important to note that all data stored on the stack has a known size at compile time.

Memory allocation in the heap is done differently than in the stack. Suppose you want to buy a shirt for a friend, but you don't know the exact size of the shirt that your friend wears, but you see him often, and you think he may be a size M or L. While you're not entirely sure, you're buying a large because even though he's a medium, your friend can still wear it. This is an important difference between the stack and the heap: we don't need to know the exact size of the values ​​stored in the heap.

There is no organization in the heap compared to the stack. Pushing data to and from the stack is easy because everything is organized and follows a specific order. The system understands that when you push a value onto the stack, it stays on top, and when you need to pop a value off the stack, you're retrieving the last value stored.

However, this is not the case in the heap. Allocating memory on the heap needs to dynamically search for a memory space large enough to satisfy the allocation requirements, and return the address pointing to the memory location. When retrieving a value, you need to use a pointer to find the memory location where the value is stored.

Allocation on the heap looks like book indexing, where pointers to values ​​stored on the heap are stored on the stack. However, the allocator also needs to search for an empty space large enough to contain the value.

Local variables of functions are stored in the function stack, while data types (such as String, Vector, Box, etc.) are stored in the heap. It's important to understand Rust's memory management to make sure your application behaves as expected.

ownership rules

There are three basic rules of ownership that predict how memory is stored on the stack and heap:

1. Every Rust value has a variable called its "owner":

let x = 5 ; // x is the owner of the value "5"

2. Each value can only have one owner at a time

3. When the owner goes out of scope, the value will be deleted:

fn main () { 
    { // // scope begins
        let s = String :: from ( "hello" ); // s comes into scope
    }  
    // the value of s is dropped at this point, it is out of scope
}

How Ownership Works

In our introduction, we established the fact that ownership is not like a garbage collector system. Most programming languages ​​either use a garbage collector or require developers to allocate and free memory themselves.

In ownership, we request memory for ourselves, and when the owner goes out of scope, the value is deleted and the memory is freed. This is exactly what the third rule of ownership explains. To better understand how this works, let's look at an example:

fn main () {
    { 
        // a is not valid here
        let a = 5 ; // a is valid here
        // do stuff with a
    } 
    println!("{}", a) // a is no longer valid at this point, it is out of scope
}

This example is very simple; this is how memory allocation on the stack works. a Since we know the exact space its value will occupy, a chunk of memory is allocated on the stack ( )5. However, this is not always the case. Sometimes, you need to allocate memory for an growable value whose size is not known at compile time.

For this case, the memory is allocated on the heap and you first have to request the memory, as shown in the following example:

fn main () { 
    { 
        let mut s = String :: from ( "hello" ); // s is valid from this point forward
        push_str ( ", world!" ); // push_str() appends a literal to a String
    	println !( "{}" , s ); // This will print `hello, world!`
    } 
    // s is no longer valid here
}

We can append as many strings as we want, since it's mutable, so it's hard to know the exact size you need at compile time. So in our program we need a memory space the size of a string:

clone and copy

In this section, we'll examine how ownership affects some features in Rust, starting with the clone and copy features.

For values ​​with a known size (like) integers, it's easier to copy a value to another. For example:_

fn main() {
    let a = "5" ; 
    let b = a ; // copy the value a into b
    println !( "{}" , a ) // 5 
    println !( "{}" , b ) // 5 
}

Because a is stored on the stack, it is easier to copy its value to make another copy of b. This is not the case for values ​​stored on the heap:

fn main () { 
    let a = String :: from ( "hello" ); 
    let b = a ; // copy the value a into b
    println !( "{}" , a ) // This will throw an error because a has been moved or ownership has been transferred
    println !( "{}" , b ) // hello 
}

When you run this command, you will receive an error error[E0382]: borrow of moved value: "a" move

Ownership and Functions

Passing values ​​to functions follows the same ownership rules, meaning they can only have one owner at a time, and memory is freed once they go out of scope. Let's look at this example:

fn main() {
    let s1 = givesOwnership(); // givesOwnership moves its return

    let s2 = String::from("hello"); // s2 comes into scope
    let s3 = takesAndGivesBack(s2); // s2 is moved into takesAndGivesBack, 
                                    // which also moves its return value into s3

} // Here, s3 goes out of scope and is dropped. s2 goes out of scope but was
  // moved, so nothing happens. s1 goes out of scope and is dropped.


fn givesOwnership() -> String { // givesOwnership will move its
                                // return value into the function
                                // that calls it

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

    someString                               // someString is returned and
                                             // moves out to the calling
                                             // function
}

// takesAndGivesBack will take a String and return one
fn takesAndGivesBack(aString: String) -> String { // aString comes into
                                                      // scope

    aString  // aString is returned and moves out to the calling function
}

slice

Refers to elements in a sequence that are adjacent to each other, rather than referring to the entire collection. Therefore, slice types can be used. However, this function does not have ownership like references and borrows.

Let's see an example below. In this example, we'll use a slice type to refer to elements of values ​​in a contiguous sequence:

fn main() {
    let s = String::from("Nigerian");
    // &str type
    let a = &s[0..4];// doesn't transfer ownership, but references/borrow the first four letters.
    let b = &s[4..8]; // doesn't transfer ownership, but references/borrow the last four letters.
    println!("{}", a); // prints Nige

    println!("{}", b); // prints rian
    
    let v=vec![1,2,3,4,5,6,7,8];

    // &[T] type
    let a = &v[0..4]; // doesn't transfer ownership, but references/borrow the first four element.
    let b = &v[4..8]; // doesn't transfer ownership, but references/borrow the last four element.
    println!("{:?}", a); // prints [1, 2, 3, 4]
    println!("{:?}", b); // prints [5, 6, 7, 8]
    
}

in conclusion

Ownership is an important feature of Rust. Mastering the concept of ownership facilitates writing scalable code. The reason why many people like Rust is because of this feature, once you master it, you can write code more efficiently.

In this article, we covered the basics of ownership, its rules, and how to apply them. In addition, it also introduces some features of Rust that do not involve ownership and how to use them cleverly. Readers who are interested in Rust's ownership features can view related documents.

- END -

About Qi Wu Troupe

Qi Wu Troupe is the largest front-end team of 360 Group, and participates in the work of W3C and ECMA members (TC39) on behalf of the group. Qi Wu Troupe attaches great importance to talent training, and has various development directions such as engineers, lecturers, translators, business interface people, and team leaders for employees to choose from, and provides corresponding technical, professional, general, and leadership training course. Qi Dance Troupe welcomes all kinds of outstanding talents to pay attention to and join Qi Dance Troupe with an open and talent-seeking attitude.

b0cb357d9882edd6e214b1e462a9b0f3.png

Guess you like

Origin blog.csdn.net/qiwoo_weekly/article/details/130376953