跳转至

所有权 1 所有者、移动、克隆

一、栈和堆

在程序运行时可供使用的内存包含 栈区堆区,对应着两种不同的数据结构 ,他们的特性不同,存储的数据也不同。

栈区 中存储的数据必须是 占用已知且固定的大小 的,而 堆区 中存储的数据是 编译时大小未知或可能变化 的。

占用已知且固定的大小 数据可以直接以 进栈 的方式存入内存,但是对于 编译时大小未知或可能变化 的数据,则需要先向 内存分配器 申请一定大小的空间,随后在 堆区 找到一块空位,标记为已使用并返回该位置的 指针 以供使用,这个过程称为 在堆上分配内存

二、所有者、移动、克隆

Rust 中,有一个 所有者 的概念:每一个值在同一时刻只能被一个 变量(所有者)拥有

而当 所有者 离开作用域时,值将被丢弃。

例子1

如果我们尝试运行下面的程序:

Rust
1
2
3
4
5
6
fn main() {
    let num1 = 7;
    let num2 = num1;

    println!("{} {}", num1, num2);
}

输出:

PowerShell
1
2
3
4
PS L:\Projects-Rust\rust-playground> cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\rust-playground.exe`
7 7

没有什么问题,程序正常运行。

例子2

但是,如果我们尝试运行下面的程序:

Rust
1
2
3
4
5
6
fn main() {
    let str1 = String::from("Hello");
    let str2 = str1;

    println!("{} {}", str1, str2);
}

编译失败:

PowerShell
PS L:\Projects-Rust\rust-playground> cargo run
   Compiling rust-playground v0.1.0 (L:\Projects-Rust\rust-playground)
error[E0382]: borrow of moved value: `str1`
 --> src\main.rs:5:23
  |
2 |     let str1 = String::from("Hello");
  |         ---- move occurs because `str1` has type `String`, which does not implement the `Copy` trait
3 |     let str2 = str1;
  |                ---- value moved here
4 | 
5 |     println!("{} {}", str1, str2);
  |                       ^^^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` (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 `rust-playground` due to previous error

会发现它无法编译。

为何?

这是为什么?还记得 所有者 的概念么?每一个值在同一时刻只能被一个 变量(所有者)拥有

这里发生的一切,其实都源自于这句话。

当你尝试将一个 将一个绑定了某一个值的变量 赋给 另一个变量 时,在其他编程语言中,你会遇到 浅拷贝深拷贝 这两个术语。

而在 Rust 中,有那么稍微的一点不同。

浅拷贝?深拷贝?移动?克隆?

例子1 大小固定的数据 —— 浅拷贝 = 深拷贝 = “克隆”

对于 例子1 中的程序,i32 类型的值较为简单大小是固定的,他们被存在 中。

程序运行时实际发生的事情是:

5 绑定到 num1; 生成一个 num1 的值的拷贝,并绑定到 num2

此时,在 中存在两个 5,他们的所有者分别是 num1num2

例子2 大小不固定的数据 —— 浅拷贝 + 标记失效 = “移动”

而对于 例子2 中的程序,String 类型大小并不固定,他们被存在 中,变量绑定到的其实是他们在 中位置的 指针

如果按照相同的逻辑,”克隆一个 str1 的值,并绑定到 str2“,那么就会导致堆中的数据 同时被两个变量拥有,而这肯定是不对的,会违反 所有者 的概念。

所以这时候 Rust 还做了一件事:

str1 标记为 无效

也就是,将 所有权str1 转交给了 str2

也因此我们无法使用 str1

那么有没有办法,将 中的数据也复制一份,也就是对其进行 深拷贝 呢?答案是有的,就是使用 clone 方法。

例子3 大小不固定的数据 —— 深拷贝 = “克隆”
Rust
1
2
3
4
5
6
fn main() {
    let str1 = String::from("Hello");
    let str2 = str1.clone();

    println!("{} {}", str1, str2);
}

clone 方法会在 上将数据复制一份,也就是常说的 深克隆

程序运行时实际发生的事情是:

使用 String::from() 中创建了一个字符串,并将其 指针 绑定到 num1

str1 对应在 中的数据 复制 一份,并将其 指针 绑定到 num2

如此就像是 例子1 一样, 中存在两个 "Hello",他们的 指针 的所有者分别是 str1str2,自然也不违反 所有者 的规则。

评论