复制值,第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 expandIDE探索由derive宏生成的代码。

参考资料

  • 本节的练习位于 exercises/04_traits/0_clone