所有权 1 所有者、移动、克隆¶
一、栈和堆¶
在程序运行时可供使用的内存包含 栈区 和 堆区,对应着两种不同的数据结构 栈 和 堆,他们的特性不同,存储的数据也不同。
栈区 中存储的数据必须是 占用已知且固定的大小 的,而 堆区 中存储的数据是 编译时大小未知或可能变化 的。
占用已知且固定的大小 数据可以直接以 进栈 的方式存入内存,但是对于 编译时大小未知或可能变化 的数据,则需要先向 内存分配器 申请一定大小的空间,随后在 堆区 找到一块空位,标记为已使用并返回该位置的 指针 以供使用,这个过程称为 在堆上分配内存。
二、所有者、移动、克隆¶
在 Rust 中,有一个 所有者 的概念:每一个值在同一时刻只能被一个 变量(所有者)拥有。
而当 所有者 离开作用域时,值将被丢弃。
例子1¶
如果我们尝试运行下面的程序:
输出:
没有什么问题,程序正常运行。
例子2¶
但是,如果我们尝试运行下面的程序:
Rust | |
---|---|
编译失败:
会发现它无法编译。
为何?¶
这是为什么?还记得 所有者 的概念么?每一个值在同一时刻只能被一个 变量(所有者)拥有。
这里发生的一切,其实都源自于这句话。
当你尝试将一个 将一个绑定了某一个值的变量 赋给 另一个变量 时,在其他编程语言中,你会遇到 浅拷贝 和 深拷贝 这两个术语。
而在 Rust 中,有那么稍微的一点不同。
浅拷贝?深拷贝?移动?克隆?¶
例子1 大小固定的数据 —— 浅拷贝 = 深拷贝 = “克隆”¶
对于 例子1 中的程序,i32
类型的值较为简单大小是固定的,他们被存在 栈 中。
程序运行时实际发生的事情是:
将
5
绑定到num1
; 生成一个num1
的值的拷贝,并绑定到num2
。
此时,在 栈 中存在两个 5
,他们的所有者分别是 num1
和 num2
。
例子2 大小不固定的数据 —— 浅拷贝 + 标记失效 = “移动”¶
而对于 例子2 中的程序,String
类型大小并不固定,他们被存在 堆 中,变量绑定到的其实是他们在 堆 中位置的 指针。
如果按照相同的逻辑,”克隆一个 str1
的值,并绑定到 str2
“,那么就会导致堆中的数据 同时被两个变量拥有,而这肯定是不对的,会违反 所有者 的概念。
所以这时候 Rust 还做了一件事:
将
str1
标记为 无效。
也就是,将 所有权 由 str1
转交给了 str2
。
也因此我们无法使用 str1
。
那么有没有办法,将 堆 中的数据也复制一份,也就是对其进行 深拷贝 呢?答案是有的,就是使用 clone
方法。
例子3 大小不固定的数据 —— 深拷贝 = “克隆”¶
Rust | |
---|---|
clone
方法会在 堆 上将数据复制一份,也就是常说的 深克隆。
程序运行时实际发生的事情是:
使用
String::from()
在 堆 中创建了一个字符串,并将其 指针 绑定到num1
;将
str1
对应在 堆 中的数据 复制 一份,并将其 指针 绑定到num2
。
如此就像是 例子1 一样,堆 中存在两个 "Hello"
,他们的 指针 的所有者分别是 str1
和 str2
,自然也不违反 所有者 的规则。