编者注:在Rust 1.0之前问过这个问题,问题中的一些断言在Rust 1.0中不一定正确 . 一些答案已更新,以解决这两个版本 .
我有这个结构
struct Triplet {
one: i32,
two: i32,
three: i32,
}
如果我将它传递给函数,则会隐式复制它 . 现在,有时我读到某些值不可复制,因此必须移动 .
是否有可能使这个结构 Triplet
不可复制?例如,是否可以实现一个特性,使 Triplet
不可复制,因此"movable"?
我读到某个地方必须实现 Clone
trait来复制那些不可隐式复制的东西,但我从来没有读过相反的东西,那就是有一些隐式可复制的东西,并使它不可复制,以便它移动 .
这甚至有意义吗?
2 回答
最简单的方法是在您的类型中嵌入不可复制的内容 .
标准库为此用例提供了"marker type":NoCopy . 例如:
前言:这个答案是在opt-in built-in traits之前编写的 - 特别是the Copy aspects - 已经实现了 . 我已经使用块引号来指示仅应用于旧方案的部分(在询问问题时应用的部分) .
类型现在默认移动,也就是说,当您定义新类型时,它不会实现
Copy
,除非您为您的类型明确实现它:只有当新
struct
或enum
中包含的每个类型本身都是Copy
时,才能存在实现 . 如果没有,编译器将打印错误消息 . 它也可以仅在类型没有Drop
实现时存在 .要回答你没有问过的问题......“移动和复制有什么用?”:
首先,我将定义两个不同的“副本”:
一个字节拷贝,它只是逐字节地复制一个对象,而不是跟随指针,例如如果你有
(&usize, u64)
,它在64位计算机上是16字节,浅拷贝将占用这16个字节并在其他一些16字节的内存块中复制它们的值,而不触及另一端的usize
.&
. 也就是说,它相当于调用memcpy
.一个语义副本,复制一个值以创建一个新的(有点)独立实例,可以安全地单独使用到旧实例 . 例如 .
Rc<T>
的语义副本只涉及增加引用计数,而Vec<T>
的语义副本涉及创建新分配,然后将每个存储元素从旧文件复制到新文件 . 这些可以是深拷贝(例如Vec<T>
)或浅(例如Rc<T>
不触及存储的T
),Clone
被宽松地定义为从&T
内部语义复制T
类型值到T
所需的最小工作量 .Rust就像C一样,每个值的使用都是一个字节副本:
它们是字节副本,无论是
T
移动还是"implicitly copyable" . (要明确的是,它们不会被保留 . )然而,字节副本存在一个基本问题:你最终会在内存中出现重复值,如果它们具有析构函数,则可能非常糟糕,例如:
如果
w
只是v
的一个普通字节副本,那么将会有两个向量指向相同的分配,两个都有析构函数释放它...导致a double free,这是一个问题 . NB . 如果我们将v
的语义副本转换为w
,那将完全没问题,因为那时w
将是它自己的独立Vec<u8>
,并且析构函数不会相互踩踏 .这里有一些可能的修复:
让程序员像C一样处理它 . (那里's no destructors in C, so it'并没有那么糟糕......你只是留下了内存泄漏 . :P)
隐式执行语义复制,以便
w
有自己的分配,就像C及其复制构造函数一样 .将按值使用视为所有权转移,以便不再使用
v
且不运行析构函数 .最后一个是Rust所做的:移动只是一个按值使用,其中源是静态无效的,因此编译器阻止进一步使用现在无效的内存 .
有类型析构函数必须在按值使用时移动(也就是在复制字节时),因为它们具有某些资源的管理/所有权(例如内存分配或文件句柄),并且字节副本不太可能正确复制此所有权 .
“嗯......什么是隐含的副本?”
想想像
u8
这样的原始类型:字节副本很简单,只需复制单个字节,语义副本就像复制单个字节一样简单 . 特别是,字节副本是一个语义副本...... Rust甚至有一个built-in trait Copy,它捕获哪些类型具有相同的语义和字节副本 .因此,对于这些
Copy
类型,按值使用也是自动语义副本,因此继续使用源是完全安全的 .如上所述,实现了opt-in内置特征,因此编译器不再具有自动行为 . 但是,过去用于自动行为的规则与检查实现
Copy
是否合法的规则相同 .