首页 > 其他分享 >Rust所有权__Ownership Rules

Rust所有权__Ownership Rules

时间:2024-04-23 18:11:06浏览次数:23  
标签:__ String Rules s2 s1 let Ownership memory scope

First, let’s take a look at the ownership rules. Keep these rules in mind as we through the examples that illustrate them:

      Each value in Rust has an owner.

      There can only be one owner at a time.

      When the owner goes out of scope, the value will be dropped.

 

Variable Scope

 

As a first example of ownership, we’ll look at the scope of some variables. A scope is the range within a program for which an item is valid. Take the following variable:

 

let s = “hello”;

 

The variable s refers to a string literal, where the value of the string is hardcoded into the text of our program. The variable is valid from the point at which it’s declared until the end of the current scope.

 

{

      let s = “hello”; // s is valid from this point forward

} // this scope is now over, and s is no longer valid

 

In other words, there are two important points in time here:

 

      When s comes into scope, it is valid.

      It remains valid util it goes out of scope.

 

At this point, the relationship between scopes and when variables are valid is similar to that in other programming languages. Now we’ll build on top of this understanding by introducing the String type.

 

The String Type

 

We’ll concentrate on the parts of String that relate to ownership. These aspects also apply to other complex data types, whether they are provided by the standard library or created by you.

 

We’ve already seen string literals, where a string value is hardcoded into our program. String literals are convenient, but they aren’t suitable for every situation in which we want to use text. One reason is that they’re immutable. Another is that not every string value can be known when we write our code: for example, what if we want to take user input and store it? For these situations, Rust has a second string type, String. This type manages data allocated on the heap and as such is able to store an amount of text that is unknown to us at compile time. You can create a String from a string literal using the from function, like so:

 

      let s = String::from(“hello”);

 

The double colon :: operator allows us to namespace this particular from function under the String type rather than using some sort of name sring_from.

 

This kind of string can be mutated:

 

      let mut s = String::from(“hello”);

      s.push_str(“, world!”);

      println!(“{}”, s);

 

So, what’s the difference here? Why can String be mutated but literals cannot? The difference is in how these types deal with memory.

 

Memory and Allocation

 

In the case of a string literal, we know the contents at compile time, so the text is hardcoded directly into the final executable. This is why string literals are fast and efficient. But these properties only come from the string literal’s immutability. Unfortunately, we can’t put a blob of memory into the binary for each piece of text whose size is unknown at compile time and whose size might change while running the program.

 

With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:

 

      The memory must be requested from the memory allocator at runtime.

      We need a way of returning this memory to the allocator when we’re done with our String.

 

That first part is done by us: when we call String::from, its implementation requests the memory it needs. This is pretty much universal in programming languages.

 

However, the second part is different. In languages with garbage collector(GC), the GC keeps track of and cleans up memory that isn’t being used anymore, and we don’t need to think about it. In most languages without a GC, it’s our responsibility to identify when memory is no longer being used and call code to explicitly free it, just as we did to request it. Doing this correctly has historically been a difficult programming problem. If we forget, we’ll waste memory. If we do it too early, we’ll have an invalid variable. If we do it twice, that’s a bug too. We need to pair exactly one allocate with exactly one free.

 

Rust takes a different path: the memory is automatically return once the variable that owns it goes out of scope.

 

      {

            let s = String::from(“hello”); // s is valid from this point forward

      }  // this scope is now over, and s is no longer valid

 

There is a natural point at which we can return memory our String needs to the allocator: when s goes out of scope. When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.

 

This pattern has a profound impact on the way Rust code is written. It may seem simple right now, but the behavior of code can be unexpected in more complicated situations when we want to have multiple variables use the data we’ve allocated on the heap. Let’s explore some of those situations now.

 

Variables and Data Interacting with Move

 

Multiple variables can interact with the same data in different ways in Rust. Let’s look at an example using an integer

 

let x = 5;
let y = x;

 

We can probably guess what this is doing: “bind the value 5 to x; then make a copy of the value in x and bind it to y.” We now have two variables, x and y, and both equals 5. This is indeed what is happening, because integers are simple values with a known, fixed size, and these two 5 values are pushed onto the stack.

 

Now let’s look at the String version:

 

let s1 = String::from(“hello”);
let s2 = s1;

 

This looks very similar, so we might assume that the way it works would be the same: that is, the second line would make a copy of the value in s1 and bind it to s2. But this isn’t quite what happens.

Figure-1

 

As shown in the above figure, a String is made up of three parts, shown on the left: a pointer to the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents.

 

The length is how much memory, in bytes, the contents of the string are currently using. The capacity is the total amount of memory, in bytes, that the String has received from the allocator. The difference between length and capacity matters, but not in this context, so for now, it’s fine to ignore the capacity.

 

When we assign s1 to s2, the String data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to. In other words, the data representation in memory looks like this:

Figure-2

 

Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop function and cleans up the heap memory for that variable. But in the above figure shows both data pointers pointing to the same location. This is a problem: when s2 and s1 go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.

 

To ensure memory safety, after the line let s2 = s1;, Rust considers s1 as no longer valid. Therefore, Rust doesn’t need to free anything when s1 goes out of scope. Check out what happens when you try to use s1 after s2 is created; it won’t work:

 

let s1 = String::from(“hello”);
let s2 = s1;
println!(“{}, world!”, s1);

 

You’ll get an error.

If you’ve heard the terms shallow copy and deep copy while working with other languages, the concept of copying the pointer, length, and capacity without coping the data probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a move. In this example, we would say that s1 was moved into s2. So, what actually happens is shown in the next figure:

Figure-3

 

Representation in memory after s1 has been invalidated, that solves our problem! With only s2 valid, when it goes out of scope it alone will free the memory, and we’re done.

 

In addition, there’s a design choice that’s implied by this: Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.

 

Variables and Data Interacting with Clone

 

If we do want to deeply copy the heap data of the String, not just stack data, we can use a common method called clone. Here’s an example of the clone method in action:

     

      let s1 = String::from(“hello”);
      let s2 = s1.clone();
      println!(“s1 = {}, s2 = {}”, s1, s2);

 

This works just fine and explicitly produces the behavior shown in Figure-3, where the heap data does get copied.

 

When you see a call to clone, you know that some arbitrary code is being executed and that code may be expensive. It’s a visual indicator that something different is going on.

 

Ownership and Functions

 

The mechanics of passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does.

 

fn main() {
      let s = String::from(“hello”); // s comes into scope
      takes_ownership(s); // s’s value moves into the function…
               // …and so is no longer valid here
      let x = 5; // x comes into scope
      makes_copy(x); // x would move into the function, but i32 is copy, so it’s okay to still
                             // use x afterward
} // Here, x goes out scope, then s. But because s’s value was moved


fn takes_ownership(str: String) { // str comes into scope
      println!(“{}”, str);
} // str goes out of scope and `drop` is called. The backing memory is freed.


fn makes_copy(integer: i32) { // integer comes into scope
      println!(“{}”, integer);
} // Here, interger goes out of scope. Nothing special happens.

 

If we tried to use s after the call to takes_ownership, Rust would throw a compile-time error. These static checks protect us from mistakes. Try adding code to main that uses s and x to see where you can use them and where the ownership rules prevent you from doing so.

 

Return Values and Scope

 

Returning values can also transfer ownership.

fn main() {
            let s1 = gives_ownership(); // gives_ownership moves its return value into s1
            let s2 = String::from(“hello”); // s2 comes into scope
            let s3 = takes_and_gives_back(s2); // s2 is moved into takes_and_gives_back, which also moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String { // gives_ownership will move its return value into the function that calls it
      let str = String::from(“yours”); // str comes into scope
      str // str is returned and moves out to the calling function
}

fn takes_and_gives_back(str: String) -> String { // str comes into scope
      str // str is returned and moves out to the calling function
}

 

The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the heap goes out scope, the value will be cleaned up by drop unless ownership of the data has been moved to another variable.

 

While this works, taking ownership and then returning ownership with every function is a bit tedious. What if we want to let a function use a value but not take ownership? It’s quite annoying that anything we pass in also needs to be passed back if we want to use it again, in addition to any data resulting from the body of the function that we might to return as well.

 

Rust does let us return multiple values using a tuple:

     

fn main() {
    let s1 = String::from(“hello”);
    let (s2, len) = calculate_length(s1);
    println!(“The length of ‘{}’ is {}”, s2, len);
}

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

 

But this is too much ceremony and a lot of work for a concept that should be common. Luckily for us, Rust has a feature for using a value without transferring ownership, called references.

标签:__,String,Rules,s2,s1,let,Ownership,memory,scope
From: https://www.cnblogs.com/ashet/p/18153493

相关文章

  • vis.js可动的3d图形
    代码案例<!DOCTYPEhtml><html><head><title>Graph3Ddemo</title><style>html,body{font:10ptarial;padding:0;margin:0;width:100%;hei......
  • 【android】获取手机安装的所有程序
     1.获取包管理器对象PackageManagerpm=context.getPackageManager();2.得到所有安装的程序包名List<PackageInfo>infos=pm.getInstallPackages(PackageManager.GET_UNINSTALLED_PACKAGES);3.然后遍历这个集合for(PackageInfopackInfo:infos){Drawabl......
  • 【专题STM32F03】FreeRTOS 队列queue传递结构体,野火例程代码简单修改。
    /************************************************************************@filemain.c*@authorfire*@versionV1.0*@date2018-xx-xx*@briefFreeRTOSV9.0.0+STM32消息队列******************************************************......
  • Blob数据转String
    importjava.io.InputStream;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;publicclassTest{   /**    *@paramargs    */   @SuppressWarnings("unused......
  • 排序4-希尔排序
    排序4-希尔排序插入排序在以下情况时效率较高当元素序列基本有序元素个数较少希尔排序是对插入排序的优化希尔排序先将元素分组(通常为总长度的一半,例如有8个数据量,则将数据分为4组,每组2个数据),然后再对每一组元素单独进行插入排序,创造出满足上述2个条件......
  • Occ中gce、GC、GCE2D构造对象的区别
    这三个名字很接近,不知道为何取名字这么容易误解。gce是构造产生gp_前缀的对象;GC是构造产生Geom_前缀的对象;GCE2D是构造产生Geom2d_前缀的对象。gce中有的类继承自gce_root;GC中有的类继承自GC_Root;GCE2D中有的类继承自GCE2d_Root。gce_root,GC_Root,GCE2d_Root中有一个gce_ErrorTy......
  • 告别手动调度,海豚调度器 3.1.x 集群部署让你轻松管理多机!
    转载自第一片心意1前言由于海豚调度器官网的集群部署文档写的较乱,安装过程中需要跳转到很多地方进行操作,所以自己总结了一篇可以直接跟着从头到尾进行操作的文档,以方便后续的部署、升级、新增节点、减少节点的相关操作。2.提前准备2.1.基础组件JDK:下载JDK(1.8+),安装并......
  • vis.js表面值3d图形
    代码案例<!DOCTYPEhtml><html><head><title>Graph3Ddemo</title><style>body{font:10ptarial;}</style><scripttype="text/javascript"src=......
  • AI agent智能体任务分解和调度的几篇经典文章
     ReAct论文解读:LLMReAct范式,在大语言模型中结合推理和动作最近在研究如何让GPT正确做动作,比如搜索内容,发现了《SYNERGIZINGREASONINGANDACTINGINLANGUAGEMODELS》这篇论文。作者提出了ReAct范式,通过将推理和动作相结合来克服LLM胡言乱语的问题,同时提高了结果的可解释性......
  • 实验3 C语言函数应用编程
    task1.c1#include<stdio.h>2#include<stdlib.h>3#include<time.h>4#include<windows.h>5#defineN8067voidprint_text(intline,intcol,chartext[]);//函数声明8voidprint_spaces(intn);//函数声明9voidpri......