编辑注释:在Rust 1.0之前询问了这个问题,并且问题中的一些断言在Rust 1.0中不一定正确。一些答案已经更新,以解决这两个版本。
我有这个结构
struct Triplet {
one: i32,
two: i32,
three: i32,
}
如果我将它传递给函数,则会隐式复制它。现在,有时我读到某些值不可复制,因此必须移动。
是否可以使此结构Triplet
不可复制?例如,是否可以实现一个特性,使Triplet
不可复制,因此可以“移动”?
我在某处读过一个必须实现Clone
特征来复制那些不可隐式复制的东西,但我从来没有读过另一种方式,那就是有一些可以隐式复制的东西并使它成为非可复制,以便它移动。
这甚至没有任何意义吗?
答案 0 :(得分:162)
前言:这个答案是在opt-in built-in traits - 特别是the Copy
aspects - 之前编写的。我使用了块引号来表示仅适用于旧方案的部分(在询问问题时应用的部分)。
旧:要回答基本问题,您可以添加一个存储
NoCopy
value的标记字段。 E.g。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
只有新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; // 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
的普通字节副本,则会有两个向量指向相同的分配,两个向量都会释放它们...导致a double free,是个问题。 NB。如果我们将v
的语义副本放到w
中,那么这将是完全没问题的,因为那时w
将是它自己的独立Vec<u8>
并且析构函数不会被践踏彼此。
这里有一些可能的修复:
w
有自己的分配,比如C ++及其副本构造函数。v
无法再使用,并且不会运行析构函数。最后一个是Rust所做的: move 只是一个按值使用,其中源是静态无效的,因此编译器会阻止进一步使用现在无效的内存。
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
具有析构函数的类型必须在按值使用时(也就是字节复制时)移动,因为它们具有某些资源的管理/所有权(例如内存分配或文件句柄)及其字节副本不太可能正确复制此所有权。
考虑像u8
这样的原始类型:字节副本很简单,只需复制单个字节,语义副本就像复制单个字节一样简单。特别是,字节副本是一个语义副本...... Rust甚至有一个built-in trait Copy
来捕获哪些类型具有相同的语义和字节副本。
因此,对于这些Copy
类型,按值使用也是自动语义副本,因此继续使用源代码是完全安全的。
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
旧:
NoCopy
标记会覆盖编译器自动行为,即假设类型可以是Copy
(即只包含基元和&
的聚合)是Copy
。但是,在opt-in built-in traits实施时,这将会发生变化。
如上所述,实现了opt-in内置特征,因此编译器不再具有自动行为。但是,过去用于自动行为的规则与检查实施Copy
是否合法的规则相同。
答案 1 :(得分:6)
最简单的方法是在您的类型中嵌入不可复制的内容。
标准库为此用例提供了“标记类型”:NoCopy。例如:
struct Triplet {
one: i32,
two: i32,
three: i32,
nocopy: NoCopy,
}