Rust:move and Borrow

Source: Internet
Author: User

It feels like the ownship,borrow and lifetime introduced in Rust's official learning document is too simplistic to really understand the causes of these grammatical designs and how to use them (especially lifetime). So find some relevant blog to see, summed up, for future reference.

Cause

The problem that Rust wants to solve is to securely manage resources without GC. This is not easy to achieve, but not a clue. For example, there is a Java program:

 Public void foo () {    bytenewbyte[10000000   a = null;
Byte[] C = new byte[10000];
}

The upper code has two places where we can explicitly tell the compiler to release the memory allocated by the previous two arrays:

    • A = null here, the array previously pointed to cannot be accessed again, so its memory can be recycled
    • The end of the Foo method}. At this point, the memory of array C can be freed.

But the reality is more complicated than that. For example, we might pass the a array to the array structure outside of Foo's local scope in the Foo method, so that a should not be freed at the end of Foo. For Java, it is only possible to recycle resources at run time through GC, and Java's syntax does not provide enough clues to allow the compiler to know when memory is released. However, this is not without benefit, because if you want to add a markup that facilitates the compiler, it can only be done by the programmer, which will undoubtedly reduce the efficiency of program development.

In the rust language, programmers need to think about the use of resources and provide information to the compiler so that the compiler checks for conflicting resource accesses at compile time, and the compiler decides when the resource is released. As a result, rust has syntax that is not available in the following three mainstream languages:

    • Ownship
    • Borrowing
    • Lifetime

Here's an overview of why you need these three grammars, each of which is responsible for solving the problem.

Ownship

First, if the compiler decides when the resource should be destroyed, then the compiler's rule must be a simple rule that is not determined by the runtime logic, such as Reference counting, which cannot be used for inspection at compile time. Rust chooses to determine the life cycle of resources by Scope/stack. When a variable leaves its scope, the resources it owns are freed. But if a resource is allowed to be owned by more than one variable, then the compiler has to use a very complex way to determine when the resource is released, or even impossible to do so. So rust stipulates that any resource can only be in one owner. This allows the compiler to determine the resource's release time only by checking the scope of the resource's owner.

Move

This "ownership" cannot be transferred if the resource is bound to its owner, which is very inflexible. The most important thing is that we cannot have a function to determine the release of the resource whose parameters are bound. Therefore, the "move" syntax is required to transfer ownership of the resource.

Borrow

There is also a lot of inconvenience if you can only move through a function parameter or other variables other than the owner to access the resource:

    • Cannot share a resource to multiple objects
    • After a function is called, the resource of the parameter to which it is being taken is not allowed to be used before the binding variable. Many times, we do not want to do this, but only a general function to modify/read this resource, after which we also want to continue to use the variables it was bound to.

Therefore, it is necessary to have a syntax that allows owner to "lend" his own ownership, which is "borrow".

However, this "loan" is more flexible than the move syntax, such as allowing multiple variables to refer to a resource, but this poses a problem with read-write conflicts. So borrow is divided into two types: immutable borrow and mutable borrow, and the compiler limits the number of borrow in a scope to avoid read-write conflicts.

Borrow actually creates a reference to the original resource, which is a pointer.

The Special one is mutable borrow, or &mut, which binds the owner to the new resource. When the target of the owner binding is changed through mutable borrow, the release of the owner's initially bound resource is triggered.

Lifetime

If the lifetime of a resource (a) is shorter than the reference (b), that is, before B expires, a is no longer accessible, then the compiler should disallow B to refer to a, otherwise it will produce "use after free error". Sometimes the relationship between A and B is easier for the compiler to find, such as

= 1= &B}
println! ("{}", a);

Sometimes, however, this relationship is not found by the compiler. For example, a is the return value of a function whose life cycle may be shorter than the reference B, or it may be a constant. If the compiler does not execute the logic of the function, it cannot determine the life cycle of a, so it cannot decide whether to use B to refer to a is safe. So, rust needs some extra markup to tell the compiler when "reference" is safe to access.

In fact, the type of each reference in rust can be considered a "composite type", and lifetime is part of it. However, programmers cannot specifically describe a reference lifetime, for example, you cannot say that "A's life cycle is from line 5th to line 8th". The value of "lifetime" must initially be written by the compiler. Programmers can only refer to the existing lifetime value by using the ' a ' tag to tell the compiler some logic about lifetime.

Ownship

Variable bindings has a property in Rust:they ' has ownership ' of what the they ' re bound to. This means if a binding goes out of scope, Rust would free the bound resources. For example:

Foo () {    vec![ 3];}  

The point is that when a variable leaves its scope, rust releases the resources it binds to. The variable that determines the life cycle of the resource is the owner of the resource.

Rust will ensure that a resource has only one owner. This looks like a read-write conflict, and you can look at the details. And with only one owner, it is clear that the compiler is also more likely to determine the timing of the release of resources.

However, if this resource "ownership" cannot be transferred, there are many problems. For example, in many cases we want to assign ownership of a resource to a function.

This logic is done by Rust's "move" syntax.

Move semantics

The move is characterized by the fact that the original variable is not available after the move. Because the function is like a black box, if you transfer ownership to the function, you cannot ensure that the variables that precede the function return can be used.

This feature of move, there are two typical examples can be shown:

Let V = vec! [1, 2, 3= v;println! ("V[0] is: {}", v[0]);

In this case, after you move the ownership of the vector to V2, you can no longer access v. So the compile time will be error

error: use of moved value: `v`println!("v[0] is: {}", v[0]);

The second is to move the resource to the function

FN Take (v:vec<i32>) {    // What happens here isn ' t important.  = vec! [1, 2, 3];take (v);p rintln! ("V[0] is: {}", v[0]);

will also report the same mistakes as above.

Borrowing

If you can access it only through the variable that the resource is bound to, there are many inconvenient places, such as reading the value of a variable in parallel. Also, if you just want to "borrow" a variable bound resource, after the loan is completed, do not want to release the resource, but the ownership "back" to the original variable, then the move syntax is somewhat inconvenient. For example in the rust documentation:

fn foo (v1:vec<i32>, v2:vec<i32>)--(VEC<I32>, vec<i32>, i32)    {// Do stuff with V1 and v2     // hand back ownership, and the result of our function    (v1, v2,vec! [1, 2, 3= vec! [1, 2, 3= foo (v1, v2);

Here, the Foo function ends without releasing the V1, V2 variable bound resources, but wants to continue using them. If you have only move syntax, you can return ownership only in the way that the function returns a value.

Borrow syntax can make this situation easier. However, it also brings new complexity.

The above example, using the borrow syntax, can do this:

 fn foo (v1: &vec<i32>, V2: &Vec<i32>)-> i32 { //  do stuff with V1 and v2     Return the answer  42}let v1  = vec! [1, 2, 3];let v2  = vec! [1, 2, 3];let answer  = foo (&v1, &v2);  //  We can use V1 and v2 here!  

Instead of taking Vec<i32> s as our arguments, we take a reference: &Vec<i32> . And instead of passing v1 v2 and directly, we pass and &v1 &v2 . We call &T The type a ' reference ', and rather than owning the resource, it borrows ownership. A binding that borrows something does is deallocate the resource when it goes out of scope. This means foo() , we original bindings again.

So, borrow actually generates a reference to the resource, which is not linked to the life cycle of the resource, which is fundamentally different from the binding.

Below, to be clear is the scope of "borrow", is from when to start borrow, to when borrow end.

Look at the following example:

Let Mut x = 5; {     = &mut x;//borrow start     *y + = 1;}//borrow end println! ("{}", x);

The scope of the borrow is determined because the borrow syntax has some requirements that are related to the scope:

    • First, any borrow must last for a scope no greater than that of the owner.
    • Second, May has one or the other of these, kinds of borrows, and not both at the same time:
      • One or more references ( &T ) to a resource,
      • Exactly one mutable reference ( &mut T ).

First, when owner cannot access it, then borrow must not be able to access it.

Second, there are only one of the following two situations:

    • One or more immutable references to a resource (&t)
    • The only mutable reference to a resource (&mut T), which means that you cannot have multiple mutable references at the same time.

The second limitation is to prevent read-write conflicts. In particular, a mutable borrowing may cause immutable borrowing access to the same resource to the wrong address, which may also cause other mutable borrowing to access the wrong address.

Like what:

fn Main () {    = 5;     = &mut x;    // -+ &mut Borrow of x starts                        here //   |    *y + = 1;           //   |                        //   |    // -+-try to borrow xhere}                      // -+ &mut Borrow of x ends here      

The above code, compile time will be error: "Cannot borrow ' X ' as immutable because it is also borrowed as mutable"

The first limitation is easy to understand, after all, if the owner can not access the reference, then of course, can not be used. Here is an example:

Let y: &i32;{     = 5;     = &x;} println! ("{}", y);

At the end of the scope of x, the borrow y of it can also be accessed, so the above code will not compile.

Reference documents

The Rust programming Languageexplore the ownership system in Rustrust borrow and Lifetimeslifetime Parameters in Rustrust Lifetimes

Rust:move and Borrow

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.