克隆(Clone)和复制(Copy)

学习内容

  • 了解和学习Rust语言引用Reference、类型与原始指针Pointer关系

篇目

复制特质Copy解释

  官方说明:

Types whose values can be duplicated simply by copying bits.

直接翻译:只需拷贝二进制数字位即可复制其值的类型。

解读:

  • 通过简单的内存拷贝,实现该类型的复制。
  • 含简单值的类型(POD,Pain Old Data),如数字类型、bool类型和引用类型,都是具有复制特质Copy的类型。
  • 只有当结构性类型内部的每个项都是Copy类型时,允许实现此类型复制特质Copy。如数组类型、tuple类型、struct类型和enum类型,它们内部的每个项都是Copy类型。
  • 当结构性类型内部的项存在不是Copy类型时,它们就不能实现该类型的复制特质。如Box类型、字符串String和向量Vec,它们内部的项不都是Copy类型。

解读延伸:

  • 复制特质Copy没有任何可实现的方法或函数。
  • 复制特质Copy的行为是不可重载的。

克隆特质Clone解释

  官方说明:

A common trait for the ability to explicitly duplicate an object.

直接翻译:能够显式地复制一个对象的通用特质。

解读:

  • 克隆特质Clone是通用的,除了不确定大小或者动态大小类型之外,适合所有类型,可实现所有类型的克隆特质Clone
  • 只有手动调用克隆特质Clone方法,才能发挥其作用。作者称之为显式行为(an explicit action)。
  • 克隆特质Clone的实现与具体类型密切相关。
  • 克隆特质Clone有可以实现的方法。

解读延伸:

  • 对于实现了复制特质Copy的类型,其克隆特质Clone与其复制特质Copy语义是一样的,等同于按位拷贝。
  • 一般情况下,Rust语言使用克隆特质Clone方法来执行对象的复制。

解释语句let y = x;

image

// {T} is a data type value;
let x = {T};
let y = x;

  上面两行代码仅仅是为了说明问题,不是Rust语言可运行代码。

  在上面代码里,当尚未确定{T}的具体类型对象值时,第二行代码的作用可能出现两种可能性。

  在Rust语里,一种称之为复制(copy)的可能性:对象xy是两个可同时独立存在的对象;另一种称之为转移或者移动(move)的可能性:对象xy是两个不能同时独立存在的对象,一旦执行了第二行代码以后,对象x不再存在了。

  下面给出具体实例说明这两种情况。第一个是所谓的复制(copy)实例,这是可运行程序。而第二个是所谓的转移(move)实例,这是不可运行程序。


# #![allow(unused_variables)]
#fn main() {
let x = 33u8;
let y = x;
println!("x = {:p}", &x);
println!("y = {:p}", &y);
#}

# #![allow(unused_variables)]
#fn main() {
let x = String::from("Hello");
let y = x;
println!("x = {:p}", &x);
println!("y = {:p}", &y);
#}

复制特质Copy和克隆特质Clone区别

表达方式不同

  复制特质Copy是以隐含方式表达,Rust语言也称之为为拷贝(copy),而克隆是一个显式行为方式表达,Rust语言也称之为克隆(clone)。

  注意:使用第一个字母大写Copy是指复制特质,而使用第一个字母小写(copy)是指复制特质Copy的行为,中文这里称之为“拷贝”或者“复制“。同理,使用第一个字母大写Clone是指克隆特质,而使用第一个字母小写(clone)是指克隆特质Clone的行为,中文这里称之为“克隆”或者”复制“。

  下面两个程序分别说明两种行为:第一个是拷贝(copy)行为,第二个是克隆(clone)行为。


# #![allow(unused_variables)]
#fn main() {
// Copy:copy behavior
// The variables x and y are different instances, and both can be used
let x = 33u8;
let y = x;
#}

# #![allow(unused_variables)]
#fn main() {
// Clone: clone behavior
// The variables x and y are different instances, and both can be used
let x = String::from("Hello");
let y = x.clone();
#}

  对于一些类型,克隆和复制行为是等价的,如静态类型u8。而对于另外一些类型,只有克隆没有复制,如类型String。当然也有类型,克隆和复制行为都存在,但是它们并是不等价的。

  对于一些类型,克隆和复制行为都实现了,如静态类型u8。而对于另外一些类型,仅克隆行为实现了,如类型String。当然自定义类型,克隆和复制行为都没有实现,需要完全自己实现或者借助于Rust语言派生属性实现。这里仅仅提供一些概念,没有完全展开说明。

内部实现不同

  复制特质Copy可以安全地复制类型对象的值。对于具有复制特质Copy的类型,编译器负责管理复制特质Copy类型的对象。

  克隆特质Clone专为程序开发任意类型复制而设计。任何类型克隆特质Clone的实现可以执行创建类型所需的任意复杂方法。像正常特质一样使用及其方法调用。

类型区别不同

  复制特质Copy适合于含值类型,如正整数u8类型。在类型u8的情况下,通过克隆无法来提高效率,这时候就不用再考虑克隆。

  克隆特质Clone适合于重量级的类型,对于一种类型,两种特质都可以实现时,要是克隆(clone)比拷贝(copy)更有效,就尽可能使用克隆(clone)。

复制特质Copy和克隆特质Clone的相互关系

  克隆特质Clone是复制特质Copy的父特质,因此特质Copy的所有内容也必须实现特质Clone。如果类型为特质Copy,则其特质Clone实现仅需要返回*self。每个特质Copy的类型也必须具有特质Clone的类型。

复制特质Copy元素的数组实例

  从下面的实例可以看到,具有复制特质Copy元素的数组也是可以简单进行移动和拷贝。具有复制特质Copy元素的数组本质上就是复制自身,因此克隆特质Clone的实现可以不需要再引用直接返回自身的传递值。


# #![allow(unused_variables)]
#fn main() {
    // File: lib-hello/src/other/clone.rs
    // Function use_clone_array()

    let a: [u8; 0] = [];
    dbg!(a);
    let b = a.clone();
    let c = a;
    dbg!(a, b, c);

    let a: [u8; 0] = [];
    dbg!(a);
    let b = a;
    let c = a.clone();
    dbg!(a, b, c);
    
#}

两种不同特质实现

  复制特质Copy是特定的编译器特征,它指示编译器,开发人员希望为该类型激活隐式复制。仅当浅表拷贝与深度拷贝等效时,此特质才可用,这样可确保不会发生内存分配作为这些隐式拷贝的一部分。复制特质Copy是一种复制的方法,仅可拷贝若干个内存地址字节的类型。

  克隆特质Clone指示编译器,开发人员创建新的对象,且必须显式调用才对象。大多数类型(但不是全部)都可以使用它进行拷贝。克隆特质Clone是也一种复制方法,可以拷贝运行任意代码的类型。

题外话

问题:什么时候我的类型应该使用复制特质Copy

  一般来说,如果一种类型可以实现复制特质Copy,则应该去实现该类型的复制特质Copy。但是如果该类型将来可能变为非复制特质Copy类型,则最好从一开始就不要实现复制特质,以避免以后发生重大更改。

  如果在编译代码时,警告显示,请添加复制特质Copy,除非有充分的理由不这样做。

问题:为什么存在克隆?

  从内部分析,克隆特质Clone的克隆(clone),与复制特质Copy的拷贝(copy)是一样的,也有拷贝(copy),只是这种拷贝(copy)资源不能让开发人员访问。为了区分两种情况,需要克隆特质Clone

  我们知道,复制特质Copy的拷贝(copy)要求可以使用堆栈中字节的简单内存来复制该值。但是克隆特质Clone的克隆(clone),不仅需要拷贝(copy)在栈(Stack)上的值(即容量、长度和指向内容的指针内存地址等),而且还需要创建(create)一个新的内存储存位置来复制其具体的内容。所以,克隆(clone)不仅仅是拷贝(copy)还有创建(create)行为,它也称之为深度拷贝(deep copy),而仅仅只有拷贝(copy)行为称之为浅表拷贝(shallow copy)。

  需要指出的是,Rust语言的克隆特质Clone的实现既可以是深度拷贝,也可以是浅表拷贝。

  比如,字符串String类型对象是可复制的,即克隆(clone)行为,请使用克隆方法clone();字符串String类型对象不可完成隐式复制的目的,即拷贝(copy)行为,因为这将导致发生非显而易见的内存分配。

参考资料