preface

It’s been a while since I wrote an article about Rust. I’m studying Rust and I’m going to make a rough generalization about the proprietary knowledge of Rust.

The ownership of

Each value in Rust has one and only one owner variable, which can be called an owner variable that has ownership of that value. Having only one owner variable ensures that Rust can reclaim and manage memory when it leaves scope, rather than using a garbage collector or manually managing memory.

Here’s an example:

fn main() {
    // create a variable with ownership
    let foo = String::from("Nctdt");
    // Foo's ownership is transferred to bar
    let bar = foo;
    println!("{}", foo); // error
    println!("{}", bar);
}
Copy the code

In this example, a borrow of Moved Value: foo error is reported, meaning that the property of the value of the foo variable has been moved, that is, to the bar variable.

As shown in the figure, the original foo variable is deprecated and no longer usable because ownership of the internal values has been transferred.

scope

Rust is a block-level scope, that is, a code block has a scope, variable declaration after the use of variables are valid within the scope. Here’s an example:

fn main() {{// a block-level scope is opened
        let s = String::from("Nctdt");
        println!("{}", s);
    } // The scope ends. External variables and values inside the scope are not accessible
    println!("{}", s); // error: not found in this scope
}
Copy the code

In this example, a scoped variable s is created. Outside the scope, the scoped variable is not accessible, but the inner scope can access the variables outside the scope and transfer ownership to it. Such as:

fn main() {
    let s = String::from("Nctdt");
    {
        s; // Move s to the inner scope, i.e. ownership is transferred
    }
    println!("{}", s); // error: borrow of moved value: `s`
}
Copy the code

In this example, the variable S is moved to the inner scope, so it is no longer accessible from the outer scope.

When data leaves scope

At the end of the scope, internal variables are automatically cleaned up for the purpose of memory cleaning and management. You can also use the Drop trait provided by Rust to perform cleanup operations.

This example requires the use of structs and traits but won’t be covered here.

struct Person {
    name: String,}impl Drop for Person {
    fn drop(&mut self) {
        println!("{} is cleared :)".self.name); }}fn main() {
    let s = Person {
        name: String::from("Nctdt"),}; { s; }// select * from 's';
      // Rust calls the Drop method of the Drop implementation of the internally owned variable when it terminates the scope, i.e. the Drop method of 's'.
    println!("S has been cleaned up.");
}
Copy the code

The output order in this example is:

Nctdt has been cleared :) s has been clearedCopy the code

Since the drop method is called when the current scope ends, Nctdt is cleared 🙂 before s is cleared.

To borrow

That doesn’t mean ownership goes around all the time, that’s too cumbersome, so you can use borrowing. Borrowing is the ability to use internal values without moving ownership. Let borrow_s = &s.

Here’s an example of not using borrowing:

fn move_string(s: String) - >String {
    println!("The ownership of {} is moved into this function.", s);
    s
}

fn main() {
    let s = String::from("Nctdt");
    let s = move_string(s);
    println!("{}", s);
}
Copy the code

In this example, ownership of S is transferred twice, first when move_string(s) is called, and the second time when move_String returns ownership of S and transfers ownership to external S, let s = move_string(s), Rust allows variables of the same name to exist, with subsequent values overwriting the previous values, although in this case the previous values are no longer available.

Use borrow instead:

fn move_string(s: &String) { println! (" borrow with {} ", s); } fn main() { let s = String::from("Nctdt"); move_string(&s); println! ("{}", s); }Copy the code

In this example, the syntax for borrowing is ampersand, ampersand String for borrowing String, and ampersand s for borrowing s. So, we’ve solved the problem of ownership moving around.

Multiple ownership smart pointer

There will always be times when multiple ownership is required, and borrowing is not appropriate, but that will not be described here. Use STD ::Rc ::Rc ::Rc; .

The pointer is used to point to the source data, and the count is used to remember how many variables have ownership of the source data. The count is a strong reference count and a weak reference count, respectively. When the strong reference count is 0 after Drop, the whole smart pointer is discarded.

use std::rc::Rc;

fn main() {
    let foo = Rc::new(1);
    let bar = Rc::clone(&foo);
    println!("foo: {}", foo);
    println!("bar: {}", bar);
    println!(
        "strong_count: {} {}",
        Rc::strong_count(&foo),
        Rc::strong_count(&bar)
    );
    let foo_weak = Rc::downgrade(&foo);
    println!("foo_weak: {}", foo_weak.upgrade().unwrap());
    println!("weak_count: {}", Rc::weak_count(&foo))
}
Copy the code

The output is:

foo: 1
bar: 1
strong_count: 2 2
foo_weak: 1      
weak_count: 1 
Copy the code

This example creates the first strong reference variable foo with Rc::new(1), and the second strong reference variable bar with Rc::clone. Rc::clone refers to the same data as the passed parameter, and the strong reference count is 2. Next, Rc::downgrade will create weak reference variable foo_weak, weak reference variable needs to be used by.upgrade(), because weak reference does not matter if the data will be discarded, only when strong reference count is 0, the data will be discarded. .upgrade() returns an Option enumeration because the data may have been discarded when using.upgrade(), and Option::None is returned when being discarded. The relationship is shown in the figure below:

The Rc internal implementation is much more complex than that, but you can think of it this way. When strong/weak reference variables leave scope, the relationship between the corresponding data is handled automatically, such as:

use std::rc::Rc;

fn main() {
    let foo = Rc::new(1);
    {
        let bar = Rc::clone(&foo);
        println!("strong_count: {}", Rc::strong_count(&foo));
        let foo_weak = Rc::downgrade(&foo);
        println!("weak_count: {}", Rc::weak_count(&foo))
    }
    println!("strong_count: {}", Rc::strong_count(&foo));
    println!("weak_count: {}", Rc::weak_count(&foo));
}
Copy the code

The output is:

strong_count: 2
weak_count: 1
strong_count: 1
weak_count: 0
Copy the code

As you can see, some of the side effects of automatically cleaning up the data whenever it leaves the corresponding scope enable data reclamation unique to Rust.

conclusion

This article gives a rough introduction to how to use multiple ownership. I hope it will be helpful.