复制值,第1部分
在上一章中,我们介绍了所有权和借用的概念,并特别指出:
- 在任何给定时间每个值都有一个所有者。
- 当函数取得某个值的所有权(“消耗它”后,调用者就无法再使用该值了。
这些限制可能有些局限性。有时我们可能需要调用一个会取走值所有权的函数,但之后仍需使用那个值。
#![allow(unused)] fn main() { fn consumer(s: String) { /* ... */ }fn example() { let mut s = String::from("hello"); consumer(s); s.push_str("!"); // 错误:值已被移动}} }
这时Clone
就登场了。
Clone
Clone
是Rust`标准库中定义的一个特性:
#![allow(unused)] fn main() { pub trait Clone { fn clone(&self) -> Self;} }
它的方法clone
接受一个引用self
并返回一个相同类型的新拥有实例。
实战
回到上面的例子,我们可以在调用clone
创建一个新的String
实例后再调用consumer
:
#![allow(unused)] fn main() { fn consumer(s: String) { /* ... */ }fn example() { let mut s = String::from("hello"); let t = s.clone(); consumer(t); s.push_str("!"); // 正常}} }
我们不是将s
的所有权交给consumer
,而是通过s
克隆创建一个新的String
(通过s.clone
)并将其给consumer
。这样s
在调用consumer
之后依然有效并可使用。
内存
让我们看看上面例子中内存里发生了什么。执行let mut s = String::from("hello");
时,内存如下:
s
+---------+--------+----------+
Stack | pointer | length | capacity |
| | | 5 | 5 |
+--|------+--------+----------+
|
|
v
+---+---+---+---+---+
Heap: | H | e | l | l | o |
+---+---+---+---+---+
执行let t = s.clone()
时,堆上分配了一个新区域以存储数据的副本:
s s
+---------+--------+----------+ +---------+--------+----------+
Stack | pointer | length | capacity | | pointer | length | capacity |
| | | 5 | 5 | | | | 5 | 5 |
+--|------+--------+----------+ +--|------+--------+----------+
| |
| |
v v
+---+---+---+---+---+ +---+---+---+---+---+
Heap: | H | e | l | l | o | | H | e | l | l | o |
+---+---+---+---+---+ +---+---+---+---+---+
如果你来自像Java这样的语言,可以把clone
想象成深拷贝对象的一种方式。
实现Clone
要让一个类型Clone
,我们必须为其实现Clone
特性。通常通过派生成功现Clone
:
#![allow(unused)] fn main() { #[derive(Clone)] struct MyType { // 字段落} }
编译器为你的MyType
实现了Clone
,如同所期望:克隆了MyType
的逐个字段并构造新MyType
。记住可以用cargo expand
或IDE
探索由derive
宏生成的代码。
参考资料
- 本节的练习位于
exercises/04_traits/0_clone