recognize ownership

Column introduction: This column serves as an entry-level article on the Rust language, and its purpose is to share programming skills and knowledge about the Rust language. For the Rust language, although the history is not as long as that of C++ and python, it can be said to have many advantages. It not only inherits the running speed of C++, but also has the memory management of Java. Personally, there is another advantage. It is the integrated compilation tool cargo, whose statement style is very similar to C++, so I personally prefer this language, and hereby create this column as a learning record to share.

Daily sharing: Work hard every day, not for anything else, just to have more choices in the future, choose comfortable days, and choose people you like!


Table of contents

Rust exclusive ownership

ownership

the stack

heap

rules of ownership

String type

memory and allocation

 Movement of variable data

literal value

String type

Cloning and copying of variable data

clone

Copy data on the stack

Ownership and Functions

return value and scope

Citation and Borrowing

mutable reference

dangling quote

quoted rules

Slice type

String literals are slices

 String slice as parameter

Other types of slices

 Summarize


Rust exclusive ownership

The feature of ownership is unique to Rust. As we said in the first chapter above, the Rust language combines the advantages of C++ and Java. In order to solve the problem that garbage in C++ cannot be recycled and it is easy to cause memory leaks, the concept of ownership is proposed in Rust. . So, understanding ownership is an essential part of mastering Rust.

ownership

One of Rust's core features is ownership . Let's get to know about ownership.

People who have studied c++ and java should know that in these two languages, c++ requires developers to allocate and release memory by themselves. In this case, many developers will use memory without releasing it, resulting in memory leaks. Java solves this problem. It uses a garbage collection mechanism to automatically find unused memory when the program is running. Rust, on the other hand, uses ownership management. To put it simply, specification checks are performed when the program is compiled to detect problems such as memory leaks in advance. In fact, this is a bit similar to the visual studio compiler under Microsoft, which is very strict on memory management.

the stack

When it comes to the stack, many people should think of the stack in the data structure. The characteristic of the stack is " first in, last out ". The memory of the stack is continuous, and the stored data is always stored in order. In the stack, the stored The data must be of fixed size, and it is already known that in C++, when memory space is opened up, it is usually opened up on the stack, so the data type must be specified to tell the system that the space opened up is fixed and of known size.

heap

The pointer we defined is in the heap area, because we don't know the specific memory size, we can only allocate a large enough memory space. . The heap is unorganized: when putting data into the heap, you request a certain amount of space. The memory allocator (memory allocator) finds a large enough space somewhere in the heap, marks it as used, and returns a pointer ( pointer ) representing the address of the location.

Therefore, when we define that the memory size of the data will not change, we open up space in the stack area. If we are not sure whether the data size will change, we open up space in the heap area.

rules of ownership

  1. Every value in Rust has an owner .
  2. A value has one and only one owner at any one time.
  3. When the owner (variable) goes out of scope, this value will be discarded.

Remember, like other languages, variable (owner) values ​​only work in the corresponding scope, and cannot be used outside the scope.

String type

Here we propose the String type to explain separately, in fact, because the String type is a bit unique, it is variable and no longer fixed, so the String class is proposed and explained separately, rather than being attributed to literal values. String type data is allocated in the heap area, so the size can change at any time.

fn main()
{
  let mut s=String::from("Hello"); //从字面值中获取字符串
  println!("{}", s); 
  s.push_str(",World");  //在字符串s后追加字符串 
  println!("{}",s);
}

For some functions in the String type, you can go to the official website to check, so I won’t introduce too much here. As for the calling method of the String type functions, it will be described later.

memory and allocation

As mentioned earlier, the String type allocates memory on the heap area. At first, the system does not know how much memory you need, so it allocates a large memory space.

  • Memory must be requested from the memory allocator at runtime.
  • StringNeed a way to return the memory to the allocator when we're done .

The first part is for us to complete, requesting memory allocation when getting the literal value, which is applicable in any language.

The second part needs to be operated according to the characteristics of different languages. For example, Java has a garbage collection mechanism, which records and clears memory that is no longer used. We don't need to care too much about memory. However, languages ​​like C++ and Python that do not have a garbage collection mechanism need to be released by themselves. For example, the analysis function in C++ will automatically release the memory after the class is created and used. This is because the analysis function automatically calls The drop() function. For some variables, we also need to release them manually, using the drop() or delete() functions.

But in Rust, when the variable leaves its scope, it will be automatically released. Rust automatically calls a special function, the drop() function, or you can call it manually yourself.

fn main()
{
  let mut s=String::from("Hello"); //从字面值中获取字符串
  println!("{}", s); 
  s.push_str(",World");  //在字符串s后追加字符串 
  println!("{}",s);
  drop(s); //释放掉内存,所有者s不再存在
}

 Movement of variable data

literal value

fn main()
{
  let s1=2;
  let s2=s1;
  println!("{}", s1);
  println!("{}", s2);
}

As in the above example, a variable s1 is first defined, then bound to the value 5, and then a variable s2 is defined to bind to s1, that is to say, the value of both variables is 5. Since they are defined when they are defined The value size has been specified, so these two variables are stored in the stack area, so they are stored in order. Both variables are valid. So what if it is stored in the heap area?

String type

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

If you run the above example, you will find that an error is reported, showing that the value of s1 does not exist. Why? Let's first introduce the concept of String type data, and then explain this problem.

Let's take s1 as an example:

Four tables: two tables representing the stack data for s1 and s2, and each points to its own copy of string data on the heap.

 

 

The data of string type consists of three parts: a pointer to the memory storing the content of the string, a length, and a capacity. This set of data is stored on the stack. On the right is the portion of memory that holds content on the heap.

The length indicates Stringhow many bytes of memory the content of is currently using. Capacity is Stringthe total number of bytes of memory fetched from the allocator.

When we bind s2 to s1:

icon

That is to say, only the data on the stack is copied, but the data on the heap is not copied, and the two variables point to the value of the heap area together. Going back to the above question, why does s1 not exist? This is because, if s1 exists, there will be two variables with the same literal value at the same time. When leaving the scope, the system will automatically call the drop() function. At this time It will appear that both variables will be released, and there will be a double free error. This is not allowed. So Rust clears s1 when s2 is bound to s1. and thus can no longer be used.

 Compiling number v0.1.0 (/home/ddb/文档/number)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:18
  |
3 |   let s1=String::from("Hello!");
  |       -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 |   let s2=s1;
  |          -- value moved here
5 |   println!("{}", s1);
  |                  ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `number` due to previous error

Cloning and copying of variable data

clone

Like other languages, Rust also provides methods for cloning. It can be understood as a deep copy, and the data in the heap area is also copied.

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

Copy data on the stack

fn main()
{
  let s1=10;
  let s2=s1;
  println!("{}", s1);
  println!("{}", s2);
}

In an example here, both variables are on the stack, so their data is copied, and both variables can exist. The data types that can be copied are:

  • All integer types, eg u32.
  • Boolean type, bool, whose values ​​are trueand false.
  • All floating point types, eg f64.
  • character type, char.
  • A tuple if and only if its contained types also implement Copy. For example, (i32, i32)implemented Copy, but (i32, String)not.

Ownership and Functions

Passing values ​​to functions is very similar to assigning values ​​to variables. So, passing values ​​to functions may be moved or copied.

fn main()
{
  let s=String::from("Hello, world!"); //s进入作用域
  String_move(s); //s的值移动到函数里,后面s不再有效
  let x=10;//x进入作用域
  number_copy(x); //x进入函数里,但是x是i32的,所以是复制进来,后面继续有效.
  println!("{}", x);
}
fn String_move(something: String) //something进入作用域
{
  println!("{}",something);
} //离开作用域,调用drop函数,something失效。
fn number_copy(number:i32) //number进入作用域,开始起作用
{
  println!("{}",number);
}//移出作用域

return value and scope

fn main()
{
  let s1=givs_ownership();
  let s2=String::from("test");
  let s3=takes_and_gives_back_ownership(s2); //s2被移动到函数中,返回值给s3,不再起作用.

} //s1,s2,s3均离开作用域,不起作用,但s2已被移走,不会发生什么。
fn givs_ownership()->String
{
  let something=String::from("something");  // "something"进入作用域
  return something;//返回"something",并移出给调用的函数
}
fn takes_and_gives_back_ownership(a_String:String)->String
{
  a_String; //返回a_String,并移出给调用函数。
}

The above two examples are all about ownership. A value can only have one owner. Therefore, different functions and different data types above use different methods, some are moving, some are cloning. At this point, we must pay attention to the distinction.

Note that the function can return multiple values ​​above the return value, but it is returned as a tuple.

fn main()
{
  let s1=String::from("hello world");
  let (num,s2)=string_length(s1);
  println!("{} {}",num,s2);

}
fn string_length(s:String)->(usize,String) 
{
  let length=s.len();
  return (length,s);
}

Citation and Borrowing

A reference ( reference ) is like a pointer because it is an address from which we can access data stored at that address that belongs to other variables. Unlike pointers, references are guaranteed to point to a valid value of a particular type.

fn main() 
{
  let s=String::from("hello");
  let length:usize =string_length(&s);
  println!("the length of the string s is: {}",length);

}
fn string_length(s: &String) -> usize
{
  return s.len();

}

 "&s" in the above function means to create a reference pointing to the data value of s, but does not own it, so there will be no such thing as ownership. So it has no effect on the original data value after leaving the scope.

In Rust, we call the act of creating a reference a borrow .

mutable reference

fn main() 
{
  let mut s=String::from("hello");
  let length:String =string_length(&mut s);
  println!("{}",s);
  println!("position is  {}",length);

}
fn string_length(s: &mut String) ->String
{
  s.push_str(",world!");
  let m:String = String::from("Successfull!");
  return m;
}

The above code is the implementation of a variable reference. We can see that a variable reference is to add a mut keyword before the reference.

As we said above, references only temporarily borrow data and do not own ownership. Therefore, when a variable is created with a variable reference, it can only be created once, otherwise an error will be reported. This is because when you create multiple When using variable references, they can all change the original data. At this time, the system will not know which one is changed in front and which one is behind. There will be chaos.

For example:

ddb@ddb-NBLK-WAX9X:~/文档/number$ cargo run
   Compiling number v0.1.0 (/home/ddb/文档/number)
error[E0499]: cannot borrow `sln` as mutable more than once at a time
 --> src/main.rs:5:10
  |
4 |   let m1=&mut sln;
  |          -------- first mutable borrow occurs here
5 |   let m2=&mut sln;
  |          ^^^^^^^^ second mutable borrow occurs here
6 |   println!("{},{}",m1,m2);
  |                    -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `number` due to previous error

 The occurrence of the above situation is a data competition in the official statement . Reasons for this:

  • Two or more pointers access the same data at the same time.
  • At least one pointer is used to write data.
  • There is no mechanism for synchronizing data access.

 In addition to not being able to have multiple mutable references at the same time, mutable and immutable references cannot exist at the same time. To give a simple example, you have a toy, someone borrows it, and then returns it as it is, but someone changes his appearance, and the toy you return is different from his, will there be conflicts?

 let mut sln=String::from("I like Rust!");
  let m1=&sln;
  let m2=&sln;
  let m3=&mut sln;
  println!("{},{},{}",m1,m2,m3);

An error will occur in a situation like the above.

dangling quote

In languages ​​with pointers, it's easy to mistakenly generate a dangling pointer by retaining a pointer to it when freeing memory , meaning that the memory it points to may have already been allocated to another holder . In Rust, by contrast, the compiler ensures that references never become dangling: when you have a reference to some data, the compiler ensures that the data doesn't go out of scope before its reference.

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

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

    &s
}

There is an error here. This is because s loses its effect after leaving the scope and no longer has any effect, so the reference must have no effect.

quoted rules

  • At any given time, there can be either only one mutable reference or only many immutable references.
  • References must always be valid.

Slice type

A slice allows you to refer to a contiguous sequence of elements in a collection instead of the entire collection. slice is a class reference, so it has no ownership.


fn main()
{
  let mut s=String::from("hello world");
  let world=first_world(&s);
  println!("{}", world);
  s.clear();  //清空字符串
}
fn first_world(s: &String) -> usize {
  let bytes = s.as_bytes();

  for (i, &item) in bytes.iter().enumerate() {
      if item == b' ' {
          return i;
      }
  }

  s.len()
}

Look at the above, the above code indicates the location of the space. For this function, a lot of library functions are used to find the index value. Is there a simple way?

Here we must mention the string slice. In Python, we can directly use the index value to find part of the string in the string. In Rust, there is also this mechanism.


fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];
    println!("{},{}", world, hello);
}

Like Python, there are many indexing methods, such as s[..3], s[..], s[2..] They respectively represent the index from 0 to position 3, from 0 to the end End, from 2 start to end.

String literals are slices

As we said earlier, the difference between a string literal and a String is that a string literal is immutable, because the data type of a string literal is &str.

For example:

let s = "Hello, world!";

The type here sis &str: it is a slice that points to a specific location in the binary. That's why a string literal is immutable; &strit's an immutable reference.

 String slice as parameter

A slice can also be used as the return data type and parameter type of a function:


fn main() {
  let s=String::from("Hello,world");
  let mun=&s[..];
  let a=Slice_from(mun);
  println!("{}",a);
}
fn Slice_from(s:&str)->&str {
  let sl:String = String::from("Hello, world");
  let world = &sl[6..11];
  let hello=&s[0..5];
  println!("{}", world);
  return hello;
}

Other types of slices

String slices, as you might imagine, are for strings. There are, however, more general slice types. Consider this array:

let a = [1, 2, 3, 4, 5]; 

Just like we want to get part of a string, we also want to reference a part of an array. We can do this:

let a = [1, 2, 3, 4, 5]; 
let slice = &a[1..3];
 assert_eq!(slice, &[2, 3]); 

 Summarize

This section has a lot of content, the main meaning is that in Rust, the Rust language provides the same way as other system programming languages ​​to control the memory you use, but has the function of automatically clearing its data after the data owner leaves the scope. This means that you don't need to write additional control code related to debugging. Therefore, as long as you know the unique mechanism of the Rust language, it will be much easier to learn.

Guess you like

Origin blog.csdn.net/qq_59931372/article/details/131752325