前言:这个答案写之前选择在内置特性 -特别的Copy
方面 -were实现。我使用块引号表示仅适用于旧方案的部分(在提出问题时适用的部分)。
旧:要回答基本问题,您可以添加存储NoCopy
值的标记字段。例如
struct Triplet {
one: int,
two: int,
three: int,
_marker: NoCopy
}
您也可以通过使用析构函数来实现此目的(通过实现Drop
trait),但是如果析构函数不执行任何操作,则首选使用标记类型。
现在默认情况下,类型会移动,也就是说,定义新类型时,Copy
除非明确为您的类型实现,否则它不会实现:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
仅当new struct
或new中包含的每个类型enum
都是其自身时,实现才可以存在Copy
。如果不是,编译器将显示一条错误消息。它也可以只在类型存在不具有Drop
实现。
要回答您没有问的问题……“移动和复制有什么关系?”:
首先,我将定义两个不同的“副本”:
- 一个字节的副本,它只是逐字节地浅层复制一个对象,而不跟随指针,例如,如果有
(&usize, u64)
,则它在64位计算机上为16字节,而浅层副本将占用这16个字节并复制它们值在其他16字节的其他内存块中,而无需触摸usize
的另一端&
。也就是说,它等效于call memcpy
。
- 一个语义复制,复制的值来创建一个新的(有点)独立,可以单独安全地用于旧的实例。例如,an的语义副本
Rc<T>
仅涉及增加引用计数,an 的语义副本Vec<T>
涉及创建新分配,然后在语义上将每个存储的元素从旧复制到新。这些可以是深层副本(例如Vec<T>
)或浅层副本(例如Rc<T>
不接触存储的T
),Clone
松散地定义为T
从a &T
到语义复制类型值所需的最少工作量T
。
Rust就像C一样,每个按值使用的值都是字节副本:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
它们是字节副本,无论是否T
移动或“隐式可复制”。(要清楚一点,它们不一定在运行时是逐字节的副本:如果保留了代码的行为,编译器可以自由地优化副本。)
但是,字节复制存在一个基本问题:您最终会在内存中获得重复的值,如果它们具有析构函数,则这可能非常糟糕,例如
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
如果w
仅仅是的纯字节副本,v
那么将有两个指向相同分配的向量,两个向量都带有释放它的析构函数...导致double free,这是一个问题。注意 这将是完全正常的,如果我们做的语义拷贝v
到w
,从此w
将自己的独立Vec<u8>
和析构函数不会对互相践踏。
这里有一些可能的修复:
- 让程序员像C一样处理它。(C中没有析构函数,所以也没有那么糟……您只剩下内存泄漏了。:P)
- 隐式地执行语义复制,以便
w
具有自己的分配,例如C ++及其复制构造函数。
- 将按值用途视为所有权转移,因此
v
不能再使用它,也不会运行其析构函数。
最后是Rust的工作:移动只是按值使用,其中源静态地无效,因此编译器禁止进一步使用现在无效的内存。
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
具有析构函数的类型在按值使用时(也就是在复制字节时)必须移动,因为它们具有某些资源(例如内存分配或文件句柄)的管理/所有权,并且字节复制不太可能正确地复制此内容所有权。
“那么……什么是隐式副本?”
考虑一下原始类型u8
:字节复制很简单,只需复制一个字节,语义复制也很简单,只需复制一个字节。特别地,字节副本是语义副本... Rust甚至具有内置的特征Copy
,可以捕获哪些类型具有相同的语义和字节副本。
因此,对于这些Copy
类型,按值使用也是自动的语义副本,因此继续使用源是绝对安全的。
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
旧的:该NoCopy
标记会覆盖编译器的自动行为,即假设可以的类型为Copy
(即仅包含基元和的集合&
)Copy
。但是,当实现了内置的内置特征时,情况将会改变。
如上所述,实现了内置的可选特征,因此编译器不再具有自动行为。但是,过去用于自动行为的规则与检查实施是否合法的规则相同Copy
。