之前的文章:
本章主要介绍 rust 的特色概念“所有权”,包括所有权的归属、转移、丢弃,以及在 rust 中“复制”的概念与应用。
所有权归属
所有权( ownership )是 rust 独创的重要概念。借助所有权,编译器可以在编译过程中就推断出变量值所占据的内存片段应在何时释放,尽可能提升内存性能。
任何数据类型值被赋值给某个变量的时候,这个变量就成为了它的“所有者”。例如:
struct Product {price: u32,kind: ProductKind,}enum ProductKind {Fruit {name: String,},Book {title: String,author: String,},}fn main() {let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};// my_product 有上述 Product 数据的所有权}
上述这段代码中, my_product 拥有了所创建的 Product 数据的所有权,也间接拥有了其下 ProductKind 数据的所有权。
所有权转移
当把一个变量赋值给另一个变量时,其实是将其值的所有权转移( move )给了另一个变量。失去所有权之后,变量就不可用了。例如:
let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};let another_product = my_product;// 所有权转移:// my_product 失去了数据的所有权;// another_product 获得了数据的所有权。println!("{}", my_product.price);// 编译失败:因为 my_product 已经失去了所有权,不能使用了
let my_product_kind = ProductKind::Fruit {name: format!("Apple"),};let my_product = Product {price: 23,kind: my_product_kind,};// 所有权转移:// my_product_kind 失去了数据的所有权;// my_product 获得了数据的所有权(间接地)。println!("{:?}", my_product_kind);// 编译失败:因为 my_product 已经失去了所有权,不能使用了
丢弃
变量作用域结束时(它所在的花括号包裹的语句块末尾),如果它还拥有一个值的所有权,那么此时这个值会被“丢弃”(不再有变量拥有它的所有权)。
rust 有一个特殊的 trait Drop ,如果数据类型实现了这个 trait ,那这个数据类型的值在被丢弃时会触发 Drop 中的一个函数 drop 。这个特性类似于一些面向对象语言中“析构函数”的概念。它的一个用途是在调试时追踪某些值是在什么时候被丢弃的,以便优化内存占用,例如:
struct Product {price: u32,kind: ProductKind,}enum ProductKind {Fruit {name: String,},Book {title: String,author: String,},}impl Drop for Product {// 这个函数在一个 Product 数据丢弃时被调用fn drop(&mut self) {println!("A product is dropped");}}fn main() {let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};println!("fn main ended");}
上例中, drop 函数中的输出在 main 函数的所有输出之后,因为 my_product 的值被丢弃是在 main 函数结束的时刻。如果想要提前丢弃一个 Product ,最常用的方法是把它单独放在一个较小的语句块内:
fn main() {{let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};}println!("fn main ended");}
像上面这样写, my_product 的值丢弃就可以早于 main 函数结束了。
注意,如果花括号包裹的语句块返回了一个值,这个值不会被丢弃,而是它的所有权转移到花括号以外,例如:
fn main() {let another_product = {let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};my_product // 这个值不会被立刻丢弃,而是转移给 another_product};println!("fn main ended");}
上例中, my_product 的值并没有在它所在的花括号末尾被丢弃,而是转移给了 another_product ;最终是在 main 函数结束的时刻被丢弃的。
借用的限制
花括号包裹的语句块内变量所有的值,不能以引用的形式返回到花括号外。即,借用的范围不能超过被丢弃的时刻。例如:
let another_product = {let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};&my_product// 编译失败:// 因为 my_product 在此时被丢弃;// 它的引用不能被返回到花括号外。};println!("fn main ended");
一个值的所有权从创建、转移到丢弃的过程,也被称为它的生命周期( lifetime )。借用范围不能超过它的生命周期。
Copy
一些常见数据类型的值,如 u32 、 f32 等数值、元组、数组,它们在被赋值时,并不会转移所有权,而是“复制”一份。例如:
fn main() {let a = 23;let b = a;// 此时并不是 a 所有权转移给 b ,而是将 a 的值复制一份给 b// 因此 a 仍然可以使用println!("{}", a);}
rust 有一个特殊的 trait Copy ,所有实现了 Copy 的数据类型值都是以复制的形式赋值,而不是所有权转移。 rust 内部已经为很多基本数据类型实现了 Copy ,但常见数据类型中的 Box 、 Vec 、 String 并没有实现 Copy 。准确地说,所有需要堆内存分配的数据类型没有实现 Copy 。
使用 struct 或 enum 定义的数据类型,只要它包含的所有数据字段都实现了 Copy ,那也可以为它简单地实现 Copy :
#[derive(Clone, Copy)]struct SimpleProduct {price: u32,}
但只要它有一个字段没有实现 Copy ,那它就不能实现 Copy 。例如, ProductKind 中包含了 String 类型字段,那它就不能实现 Copy ;而 Product 中包含了 ProductKind 类型字段,进而 Product 也不能实现 Copy ;否则会编译失败。
#[derive(Clone, Copy)]struct Product {price: u32,kind: ProductKind,}#[derive(Clone, Copy)]enum ProductKind {Fruit {name: String,},Book {title: String,author: String,},}// 编译失败!这两个类型都不能实现 Copy
Clone
如果一个类型不能实现 Copy ,但又希望它能够在赋值时被复制,那可以转而为它实现 Clone 。Clone 与 Copy 的区别是, Clone 一定需要在赋值时明确使用 .clone() 来复制,例如:
#[derive(Clone)] 为数据类型实现 Clonestruct Product {price: u32,kind: ProductKind,}#[derive(Clone)] // 为数据类型实现 Cloneenum ProductKind {Fruit {name: String,},Book {title: String,author: String,},}fn main() {let my_product = Product {price: 23,kind: ProductKind::Fruit {name: format!("Apple"),},};let another_product = my_product.clone();// my_product 是以复制的形式赋值的,并不是所有权转移// 因此 my_product 仍然可用println!("{}", my_product.price);}
实际上, Clone 相比于 Copy 能用于更多数据类型上(所有实现了 Copy 的数据类型同时也实现了 Clone )。但是 Clone 可能有更大开销,比如会将 String 类型的字符串内容完整复制一次。因此,编译器也要求 Clone 进行复制时必须要明确写上 .clone() 。
此外, Clone 的具体复制过程也可以被自定义,使用 impl Clone 代替 derive(Clone) 即可:
struct Product {price: u32,kind: ProductKind,}impl Clone for Product {fn clone(&self) -> Self {Self {price: self.price,kind: self.kind.clone(),}}}
相比于变量值的生命周期,引用的生命周期更为复杂,这将在之后的文章中介绍。




