🎯所有权规则
重要:
- Rust 中的每一个值都有一个 所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
🎯变量作用域
作用域(scope)。作用域是一个项(item)在程序中有效的范围。
{ // s 在这里无效,它尚未声明 let s = "hello"; // 从此处起,s 是有效的 // 使用 s } // 此作用域已结束,s 不再有效
换句话说,这里有两个重要的时间点:
- 当
s
进入作用域 时,它就是有效的。- 这一直持续到它 离开作用域 为止。
目前为止,变量是否有效与作用域的关系跟其他编程语言是类似的。
🎯String类型
String比哪些基础的标量数据类型更复杂。
字符串字面值:程序里手写的哪些字符串值:
- 他们是不可变的。
- 非所有字符串的值都能在编写代码时就知道
这个类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。
let s = String::from("hello");
这两个冒号
::
是运算符,允许将特定的from
函数置于String
类型的命名空间(namespace)下,而不需要使用类似string_from
这样的名字。let mut s = String::from("hello"); s.push_str(", world!"); // push_str() 在字符串后追加字面值 println!("{}", s); // 将打印 `hello, world!`
🎯内存和分配
就字符串字面值来说,我们在编译时就知道其内容,所以文本被直接硬编码进最终的可执行文件中。这使得字符串字面值快速且高效。不过这些特性都只得益于字符串字面值的不可变性。
对于
String
类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:
- 必须在运行时向内存分配器(memory allocator)请求内存。
- 需要一个当我们处理完
String
时将内存返回给分配器的方法。Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。
当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String 的作者可以放置释放内存的代码。Rust 在结尾的 } 处自动调用 drop。
🎯变量和数据交互的方式:移动(Move)
在 Rust 中,多个变量可以采取不同的方式与同一数据进行交互。
let x = 5; let y = x;
这也正是事实上发生了的,因为整数是有已知固定大小的简单值,所以这两个
5
被放入了栈中。
1. let s1 = String::from("hello"); 2. let s2 = s1;
这看起来与上面的代码非常类似,所以我们可能会假设它们的运行方式也是类似的:也就是说,第二行可能会生成一个 s1 的拷贝并绑定到 s2 上。不过,事实上并不完全是这样。
如下图所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。
我们将
s1
赋值给s2
,String
的数据被复制了,这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上数据。
重点:之前我们提到过当变量离开作用域后,Rust 自动调用 drop 函数并清理变量的堆内存。不过图展示了两个数据指针指向了同一位置。这就有了一个问题:当 s2 和 s1 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是叫做浅拷贝。
因为只有
s2
是有效的,当其离开作用域,它就释放自己的内存,完毕。
🎯变量和数据交互的方式:克隆(clone)
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);
原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 clone 并不会与通常的浅拷贝有什么不同。
任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:
- 所有整数类型,比如
u32
。- 布尔类型,
bool
,它的值是true
和false
。- 所有浮点数类型,比如
f64
。- 字符类型,
char
。- 元组,当且仅当其包含的类型也都实现
Copy
的时候。比如,(i32, i32)
实现了Copy
,但(i32, String)
就没有。