Rust ownership


Release date:2023-11-08 Update date:2023-11-08 Editor:admin View counts:185

Label:

Rust ownership

Computer programs must manage the memory resources they use at run time.

Most programming languages have the ability to manage memory:

Languages such as CUniverse + mainly manage memory manually, and developers need to manually apply for and release memory resources. However, in order to improve the development efficiency, as long as it does not affect the implementation of program functions, many developers do not have the habit of releasing memory in time. So the way of managing memory manually often results in a waste of resources.

The program written in Java language runs in the virtual machine (JVM), and JVM has the function of automatically reclaiming memory resources. But this approach often reduces runtime efficiency, so JVM will recycle as few resources as possible, which will also make the program take up more memory resources.

Ownership is a novel concept for most developers, and it is the syntax mechanism designed by the Rust language to use memory efficiently. The concept of ownership is developed to enable Rust to analyze the usefulness of memory resources more effectively during the compilation phase in order to achieve memory management.

Ownership rule

There are three rules for ownership:

  • Each value in Rust has a variable called its owner.

  • There can be only one owner at a time.

  • This value is deleted when the owner is not in the scope of the program.

These three rules are the basis of the concept of ownership.

Next, we will introduce concepts related to the concept of ownership.

Variable range

We use the following program to describe the concept of variable scope:

{
//Invalid variable s before declaration
Lets="runoob";
//Here is the available range of variable s
}
//Variable range has ended, variable s is invalid

A variable scope is an attribute of a variable that represents the feasible domain of the variable and is valid by default from declaring the variable to the end of the variable’s domain.

Memory and allocation

If we define a variable and assign it a value, the value of that variable exists in memory. This situation is very common. But if the length of the data we need to store is uncertain (such as a string entered by the user), we cannot specify the length of the data at the time of definition, and we will not be able to make the program allocate a fixed length of memory space for data storage during the compilation phase. Some people say that allocating as much space as possible can solve the problem, but this method is very uncivilized. This needs to provide a mechanism for the program to apply for memory while the program is running-heap. All the “memory resources” in this chapter refer to the memory space occupied by the heap.

Where there is allocation, there is release, and programs cannot occupy a certain memory resource all the time. Therefore, the key factor to determine whether resources are wasted is whether resources are released in a timely manner.

Let’s write the sample string program in C language equivalent:

{
    char *s = strdup("runoob");
    free(s); // Release s resources
}

Obviously, there is no call in Rust free function to release the resources of the string s (I know this is not the correct way to write in C because “runoob” is not in the heap, so let’s assume it is). The reason why Rust does not explicitly release the step is that when the variable range ends, the Rust compiler automatically adds the step of calling the release resource function.

This mechanism seems simple: it just helps programmers add a function call that frees resources in the right place. But this simple mechanism can effectively solve one of the most troublesome programming problems in history.

The way variables interact with data

There are two main ways for variables to interact with data: Move and Clone:

move

Multiple variables can interact with the same data in different ways in Rust:

let x = 5;
let y = x;

This program binds the value 5 to the variable x, then copies the value of xand assigns it to the variable y. There will now be two values of 5 in the stack. The data in this case is of the “basic data” type and does not needto be stored in the heap, and the “move” mode of the data in the stack iscopied directly, which does not take longer or more storage space. The basic data types are as follows:

  • All integer types, such as i32u32i64 , etc.

  • Boolean type bool with a value of true or false .

  • All floating point types f32 and f64 .

  • Character type char .

  • A tuple that contains only the above types of data.

But if the interactive data is in the heap, it is a different situation:

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

The first step is to produce a String object with a value of “hello”.Where “hello” can be thought of as data of uncertain length, which needs to be stored in the heap.

The situation in the second step is slightly different (this is not entirelytrue, it is only for comparative reference):

Image0

As shown in the figure: two String object in the stack, each String objects have a pointer to the “hello” string in the heap. In giving s2 when assigning, only the data in the stack is copied, and thestring in the heap is still the original string.

As we said earlier, when a variable is out of range, Rust automatically calls the free resource function and cleans up the variable’s heap memory. But s1 and s2 if both are released, the “hello” in the heap areais released twice, which is not allowed by the system. In order to ensure safety, give s2 when assigning values s1 is no longer effective.That’s right. I’m putting s1 value assigned to the s2 later, s1 will no longer be available for use. The following procedure is wrong:

let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // Error! S1 has expired

So the actual situation is:

Image1

S1 exists in name only.

Clone

Rust reduces the cost of running the program as much as possible, so by default, longer data is stored in the heap and the data is exchanged in a mobile manner. But if you need to simply make a copy of the data for other use, you can use the second way to interact with the data-cloning.

Example

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

Running result:

s1 = hello, s2 = hello

Here is a real copy of the “hello” in the heap, so s1 and s2 Each is bound to a value, and when released, it is treated as two resources.

Of course, cloning is only used when replication is needed, since it takes more time to replicate data.

Ownership mechanism involving functions

This is the most complicated case for variables.

How do you safely handle ownership if you pass a variable to another function as an argument to the function?

The following program describes how the ownership mechanism works in this case:

Example

Fn main(){
Lets=String:: from ("hello");
//S declared valid
Take_ Ownership (s);
//The value of s is passed into the function as a parameter
//So it can be considered that s has been moved and is no longer valid from here on
Let x=5;
//X declared valid
Make_ Copy (x);
//The value of x is passed into the function as a parameter
//But x is the basic type and still valid
//You can still use x here, but not s
}//Function ended, x invalid, followed by s. However, s has been moved, so it does not need to be released
Fn takes_ Ownership (some_string: String){
//A String parameter some_ String passed in, valid
Println! ({} ", some_string);
}//Function ended, parameter some_ String is released here
Fn makes_ Copy (some_integer: i32){
//An i32 parameter some_ Incoming integer, valid
Println! ({} ", some_integer);
}//Function ended, parameter some_ Integer is a basic type and does not need to be released

If you pass a variable into a function as an argument, it has the same effect as moving.

Ownership mechanism of function return value

Example

Fn main(){
Let s1=gifts_ Ownership();
//Gives_ Ownership moves its return value to s1
Let s2=String:: from ("hello");
//S2 declared valid
Let s3=takes_ And_ Gives_ Back (s2);
//S2 is moved as a parameter, and s3 obtains ownership of the return value
}//s3 is invalid and released, s2 is moved, and s1 is invalid and released
Fn gifts_ Ownership() ->String{
Let some_ String=String:: from ("hello");
//Some_ String declared valid
Return some_ String;
//Some_ String is moved out of the function as a return value
}
Fn takes_ And_ Gives_ Back (a_string: String) ->String{
//A_ String declared valid
A_ String//a_ String is used as a return value to move out of the function
}

The ownership of the variable that is treated as the return value of the function will be moved out of the function and returned to the place where the function is called, rather than being invalidly released directly.

Citation and lease

Reference is a concept that C++ developers are familiar with.

If you are familiar with the concept of pointer, you can think of it as a pointer.

In essence, “reference” is the indirect access to variables.

Example

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("s1 is {}, s2 is {}", s1, s2);
}

Running result:

s1 is hello, s2 is hello

The & operator can take a reference to a variable.

When the value of a variable is referenced, the variable itself is not considered invalid. Because reference does not copy the value of the variable in the stack:

Image2

Function arguments are passed in the same way:

Example

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
    s.len()
}

Running result:

The length of 'hello' is 5.

The reference does not take ownership of the value.

Reference can only lease (Borrow) the ownership of values.

The reference itself is also a type and has a value that records the location of other values, but the reference does not have ownership of the specified value:

Example

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    let s3 = s1;
    println!("{}", s2);
}

This procedure is incorrect: because s2 leased s1 ownership has been moved to s3 , so s2 will not be able to continue to lease s1 ownership of. If you need to use the s2 with this value, you must re-lease:

Example

fn main() {
    let s1 = String::from("hello");
    let mut s2 = &s1;
    let s3 = s1;
    s2 = &s3; // Renting ownership from s3
    println!("{}", s2);
}

This procedure is correct.

Since the reference does not have ownership, even if it rents ownership, it only has the right to use it (which is the same as renting a house).

Attempts to modify data using leased rights will be blocked:

Example

fn main() {
    let s1 = String::from("run");
    let s2 = &s1;
    println!("{}", s2);
    s2.push_str("oob"); // Error, it is prohibited to modify the rental value
    println!("{}", s2);
}

In this program, s2 try to modify, s1 is blocked and the ownership of the lease cannot modify the owner’s value.

Of course, there is also a variable lease, just like you rent a house, if the property stipulates that the owner can modify the structure of the house, and the owner also declares this right in the contract when renting, you can redecorate the house:

Example

fn main() {
    let mut s1 = String::from("run");
    // S1 is variable
    let s2 = &mut s1;
    // S2 is a variable reference
    s2.push_str("oob");
    println!("{}", s2);
}

There will be no problem with this procedure. We use &mut modifies a variable reference type.

Mutable references do not allow multiple references compared to immutable references except for permissions, but immutable references can:

Example

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);

This program is incorrect because s is referenced by multiple variables.

Rust’s design of variable references is mainly based on the consideration ofdata access collisions in the concurrent state, which is avoided during thecompilation phase.

Since one of the necessary conditions for data access collisions is that thedata is written by at least one user and read or written by at least one other user at the same time, it is not allowed to be referenced again when avalue is variably referenced.

Dangling References

This is a concept with a different name, and if you put it in a programming language with a pointer concept, it refers to a pointer that doesn’t actually point to data that can actually be accessed (note, it’s not necessarily a null pointer, it could also be a resource that has been released). They are like ropes that lose hanging objects, so they are called”hanging references”.

A “dangling reference” is not allowed in the Rust language, and if so, thecompiler will find it.

Here is a typical case of overhanging:

Example

fn main() {
    let reference_to_nothing = dangle();
}
fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

Obviously, along with dangle at the end of a function, the value of itslocal variable itself is not treated as a return value and is released. However, its reference is returned, and the value pointed to by this reference can no longer be determined, so it is not allowed.

Powered by TorCMS (https://github.com/bukun/TorCMS).