Rust常用特型之Clone+Copy特型

Rust常用特型之Clone+Copy特型。在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。

今天,我们把Clone 和 Copy 特型放在一起学习。

(注:本文更多的是对《Programing Rust 2nd Edition》的自己翻译和理解,并不是原创)

一 什么是Clone

标准库的Clone特型意味着实现它的类型可以制作一份值的副本。它的定义如下:

trait Clone: Sized {
  fn clone(&self) -> Self;
  fn clone_from(&mut self, source: &Self) {
    *self = source.clone()
  }
}

它有两个函数,其中一个函数有默认实现,因此我们只需要自己实现第一个函数clone()就好,实际上,运用的最多的也是clone()函数。

clone()函数会构造自身的独立备份并且返回它。因为该函数的返回类似是Self, 因此函数不可能返回unsized值。这是因为Clone特型本身拓展了Sized特型,因此,实现了Clone特型的类型必须是Sized

克隆一个值通常需要为它所拥有的任何内容重新分配资源,所以克隆在时间和内存上都开销很大。例如, 克隆一个Vec<String> 不仅仅复制了向量本身,也复制了它的每一个字符串元素。这就是为什么Rust不自动实现克隆,而需要你手动显式调用的原因。引用计数指针类型例如Rc<T>andArc<T>是个例外,克隆他们只是简单的增加一个引用计数,并且返回给你一个新的指针。

clone_from函数从一个源值复制,它的默认实现很简单,只是调用了source的clone函数。因为函数参数self的类型是&mut,因此这里只需要重新赋值即可*self = value。这样做是没有问题的,但是某些场景下,有更快的方法来达到同样的效果。

例如 假定st都是String,语句s = t.clone() 它首先克隆t,然后drops的旧值,然后移动新复制的值到s。这里涉及到堆的分配和释放两种操作。但是如果指向最开始s的堆缓冲区有足够的容量来容纳t的元素,那么我们就不需要重新分配或者释放堆。你只是简单复制t的文本到s的缓冲区并修改长度即可。

在通用代码中,您应该尽量使用clone_from以利用可能存在的优化。这里我前面说错了,我们常用clone函数,但是这里更推荐clone_from函数,虽然它可能会多拼一点.这里的真实意思其实是这样的

我们首先看clone_from的注释(vscode中的tooltips):

Performs copy-assignment from source.

a.clone_from(&b) is equivalent to a = b.clone() in functionality, but can be overridden to reuse the resources of a to avoid unnecessary allocations.

上面解释的很清楚了,这里再说一下,有下面两种情况:

  1. s 之前未定义或者未被初始化过 ,此时s无法转换为self,因为不存在或者未初始化,只能使用s = t.clone()

  2. s 之前初始化过,需要重新赋值,这时可以使用 clone_from以便可能进行优化。

    let mut s = "Hello World".to_owned();
    let t = "Hello Hi".to_owned();
    s.clone_from(&t);
    // Clone::clone_from(&mut s, &t); // 这种方式也是可行的
    // String::clone_from(&mut s, &t); // 这种方式也是可行的
    println!("s: {s}");
    

    这里也可以看出特型的几种调用方式:

    1. 值调用
    2. 特型名称调用
    3. 类型名称调用

    其中 2和3 相当于类的静态方法,1相当于类的实例上的方法

如果你的克隆实现只是简单的复制你的类型中的每个字段或者元素,并且默认的clone_from也很适用,那么Rust可以自动帮你实现Clone特型,你只需要在你的类型定义上面加上#[derive(Clone)]就行。

在标准库中,几乎所有有确切复制含义的类型都实现了Clone特型。例如像booli32这样的元数据类型实现了Clone,像StringVec<T>HasMap等容器类型也实现了Clone。有些类型没有明确的复制含义,例如std::sync::Mutex,它们没有实现Clone特型。一些类型可以复制,但是复制可能失败(操作系统有可能无法拥有必需的资源),例如std::fs::File类型,也没有实现Clone,因为clone函数是不能失败的。 作为替代方案,std::fs::File提供了一个try_clone函数,它返回一个Result<File>,它可以用来处理失败情况。

二 Copy

Clone是复制,Copy也是复制,那么很多人肯定和我一样有疑惑,这两者有什么区别呢?不急,我们先看书中怎么说的。

对于绝大多数类型来说,赋值是move值,而不是复制他们。移动一个值使得追踪他们拥有的资源更简单。但是也有例外,不拥有任何资源的类型可以是Copy类型,此时赋值仅是对源值的复制,并不会移动源值并让原变量变成未初始化状态。

Copy类型是指实现了std::marker::Copy特型的类型,该特型定义如下:

trait Copy: Clone { }

可以看出,定义非常简单,Copy特型只是简单扩展了一下Clone,并没有定义新的方法。也就是说,Copy类型一定是Clone的,而Clone类型却未必是Copy的。例如String,它是Clone的,但是不是Copy的。 u32类型,上面提到过,它是Clone的,同时也是Copy的。

那么Copy类型为什么也要是Clone类型呢?因为复制行为必须依赖Clone实现。

在Rust中,这种简单特型扩展的用法很常见,你可以利用它从一类类型中(Clone类型)再划出一个小类型(Copy类型)。

如果想对自己的类型实现Copy特型,只需要这么做

impl Copy for MyType { }

因为Copy特型是一个标记类型,对Rust语言来说有特殊的含义,因此,Rust中Copy类型的复制行为只能是字节到字节的复制。如果类型拥有其它资源,例如堆缓冲区和操作系统句柄,他们是不能实现Copy类型的。

(那么这个约束是怎么强制的?应该是编译器检查的)

在学习Drop特型的时候提到,任何实现了Drop特型的类型不能再是Copy类型,Rust会假定如果一个类型需要特殊的清理逻辑,那么它必定需要特殊的复制机制,因此不能实现Copy

Clone一样,你可以让Rust自动为你复现Copy特型,方式为使用#[derive(Copy)]语法。你也会经常看到一次性的derive两个特型:#[derive(Copy, Clone)] 。这两种方式的区别是,如果使用方式一,那么先必须为该类型实现了Clone特型,如果使用方式二,则一次性完整,大多数情况下使用方式二。

// 方式一
#[derive(Copy)]
struct MyType;

impl Clone for MyType {
    fn clone(&self) -> Self {
        Self {  }
    }
}

// 方式二

#[derive(Clone, Copy)]
struct MyType;

记住,你不能为String实现Copy特型,因为两者都是外部的。一个结构体,只有所有字段是Copy特型时才能是可Copy的,例如

#[derive(Clone, Copy,Debug)]
struct MyType {
    name:String
}

这段代码会提示你该类型无法复制Copy。我们把name字段换一下就可以了,如下:

#[derive(Clone, Copy,Debug)]
struct MyType {
    age:u32
}

对于上面的MyType,我们运行如下代码片断:

let a = MyType {age:10};
let b = a;
println!("{a:?},{b:?}");

会输出MyType { age: 10 },MyType { age: 10 }。 这里可见发生了Copy而不是Move

注意:

谨慎考虑将一个类型设计为Copy类型,虽然这么做使用该类型会比较简单,但是对它的实现有比较大的限制。并且,复制是比较昂贵的。

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AiMateZero

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值