Rust所有权与借用图示概念


Rust 中的所有权是什么?
Rust没有垃圾收集器,您需要显式分配和释放内存空间。当涉及大型代码库时,这很快就会变得乏味和具有挑战性。
传统上,有两种管理内存的基本方法:

  • 第一个是垃圾收集器;它主要用于从程序员那里抽象出内存管理概念的高级语言中。
  • 第二种是手动内存管理,程序员明确定义内存使用。虽然它提供了控制,但它留下了很大的空间让自己在脚下开枪。

Rust 采用了另一种方法,称为所有权和借用。所有权是一种新的结构,它定义了一个值  且有一个所有者。

让我们看看下面这段代码和它的输出

change the value of i=11
print the value of i=10, after let_change_the_value_by_one call

当我们将i传递给函数let_change_the_value_by_one时,将值增加1并打印它,当控件返回main() 函数并打印它时,我们得到与以前相同的值。

与字符串String不同, 整数存储在栈Stack中而不是堆Heap中,当把i传给函数let_change_the_value_by_one时,编译器会复制一个值并把它传给函数,实际值保持不变。在所有权方面,变量i是栈中数值的所有者,在其作用域内一直保持有效。
现在,我们如何将变量从更改i8为string发生的情况

18 |     let value_for_i = String::from("Rust");
   |----------- move occurs because `value_for_i` has type `String`, which does not implement the `Copy` trait
19 |     lets_change_the_value_of_string(value_for_i);
   |----------- value moved here
20 |     println!(
"print the value of value_for_i={}, after lets_change_the_value_of_string call", value_for_i)
   |                                                                                               ^^^^^^^^^^^ value borrowed here after move

现在当涉及到Heap堆时,所有权的概念将更加透明。
当变量value_for_i传递给函数lets_change_the_value_of_string时,编译器在Stack栈中创建了一个变量引用的副本,并将所有权转移到它。
Heap堆中的值没有发生任何变化。一旦控制权回到main(),变量value_for_i的引用就不存在了,这就是编译器试图告诉我们移动已经发生的原因。

|----------- 发生移动是因为 `value_for_i` 的类型为 `String`,它没有实现 `Copy` 特征
| let_change_the_value_of_string(value_for_i); 
|----------- 值已移至此处

这是原理插图:

简而言之,Rust作用域内的任何变量都是值的所有者,它不能在其所有权之外更改。所有权需要由程序员自己手动管理。
为了使这段代码工作,我们可以将value_for_i.clone()传递给函数,这将创建一个引用和值的克隆。

但是,如果我们不想转移所有权而是想修改相同的值怎么办。通过使用借贷的概念如何实现这一点

Rust中的借用是什么
在Rust中,我们可以从Stack中借用变量的引用,然后使用借用的引用对Heap中的值执行操作。这样我们就完全不用处理所有权的转移。为了借用value_for_i的引用,我们使用借用操作符&,把像&value_for_i这样的引用借用传递给函数。 

print the value of value_for_i=Rust, before lets_change_the_value_of_string call
The value of variable value_for_i=Rust is awesome
print the value of value_for_i=Rust is awesome, after lets_change_the_value_of_string call

上面的例子我们正在做很多事情:

  1. 将局部变量value_for_i改为可变
  2. 将函数的参数改为&mut value_for_i , 这样就可以传递引用,同时也使其成为可变的。
  3. 使用push_str()和value_for_i来扩展字符串的值。
  4. 现在当控件回到main()并执行println!时,我们得到了修改后的字符串。

这解释了我们如何使用借用概念来处理我们不想转移所有权的价值。虽然借用概念有一些限制。

我们只能在其作用域内为变量创建一次可变引用。不可变引用没有限制。如果发现悬空引用,编译器也会抱怨,这意味着编译器将确保数据在引用之前不会超出作用域。