前言:本答案是在
内置特质中的 opt-in,尤其是
Copy
特质 实现之前编写的。我使用块引用来指示仅适用于旧方案的部分(即在提问时适用的方案)。
Old: To answer the basic question, you can add a marker field storing a NoCopy
value. E.g.
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
You can also do it by having a destructor (via implementing the Drop
trait), but using the marker types is preferred if the destructor is doing nothing.
现在类型默认移动,也就是说,当你定义一个新类型时,它不会实现
Copy
,除非你显式地为该类型实现它:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {}
实现只有在新的
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 语言,对值的每个按值使用都是一个字节的复制:
let x: T = ...;
let y: T = x;
fn foo(z: T) -> T {
return z
}
foo(y)
无论
T
是否移动或是“隐式可复制的”,它们都是字节拷贝。(需要明确的是,如果代码的行为得以保留,编译器可以自由地优化掉运行时的逐字节拷贝。)
然而,字节拷贝存在一个根本性问题:在内存中会有重复值,如果它们有析构函数,则可能非常糟糕。
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
}
如果
w
只是
v
的一个普通字节复制,那么将会有两个指向同一分配的向量,它们都有析构函数释放它...导致
双重释放,这是一个问题。请注意,如果我们对
v
进行语义复制并将其复制到
w
中,则这将是完全可以的,因为
w
将成为自己独立的
Vec<u8>
,而析构函数不会互相踩踏。
这里有几种可能的修复方法:
- 让程序员像C一样处理它(C中没有析构函数,所以情况没有那么糟糕...只会留下内存泄漏。:P)
- 隐式执行语义复制,使
w
有自己的分配,就像C++的复制构造函数一样。
- 将按值使用视为所有权的转移,使
v
不能再使用并且不会运行其析构函数。
最后这就是 Rust 的作用:移动(move)仅仅是按值使用并且源被静态无效化,因此编译器防止对无效内存的进一步使用。
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v);
具有析构函数的类型
必须在按值使用时移动(即在字节复制时),因为它们管理/拥有某些资源(例如内存分配或文件句柄)并且很难通过字节复制正确地复制此所有权。
"那么...什么是隐式复制?"
考虑像u8
这样的原始类型:字节复制很简单,只需复制单个字节,语义复制也同样简单,复制单个字节。特别地,字节复制就是语义复制...Rust甚至有一个内置特质Copy
,用于标识哪些类型具有相同的语义和字节复制。
因此,对于这些Copy
类型的按值使用也自动成为语义复制,因此继续使用源是完全安全的。
let v: u8 = 1;
let w: u8 = v;
println!("{}", v);
旧文:`NoCopy` 标记覆盖了编译器的自动行为,即假定只包含原始类型和 `&` 的聚合体类型是可复制的(即 `Copy`)。然而,当实现
选择内置特性 时,这将发生变化。
如上所述,已经实现了选择内置特性,因此编译器不再具有自动行为。但是,过去用于自动行为的规则与检查是否可以实现 `Copy` 的规则相同。