编者注:在Rust 1.0之前问过这个问题,问题中的一些断言在Rust 1.0中不一定正确.一些答案已更新,以解决这两个版本.
我有这个结构
struct Triplet {
one: i32,
two: i32,
three: i32,
}
Run Code Online (Sandbox Code Playgroud)
如果我将它传递给函数,则会隐式复制它.现在,有时我读到某些值不可复制,因此必须移动.
是否有可能使这个结构Triplet
不可复制?例如,是否有可能实现一个Triplet
不可复制的特性,因此可以"移动"?
我读到某个地方必须实现Clone
特征来复制那些不能隐式复制的东西,但是我从来没有读过相反的东西,那就是有一些隐式可复制的东西,并使它不可复制,以便它移动.
这甚至有意义吗?
huo*_*uon 163
前言:这个答案是在选择加入内置特征之前编写的- 特别是Copy
方面 - 已经实现了.我已经使用块引号来指示仅应用于旧方案的部分(在询问问题时应用的部分).
旧:要回答基本问题,您可以添加存储
NoCopy
值的标记字段.例如Run Code Online (Sandbox Code Playgroud)struct Triplet { one: int, two: int, three: int, _marker: NoCopy }
您也可以通过使用析构函数(通过实现
Drop
特征)来实现,但如果析构函数不执行任何操作,则首选使用标记类型.
类型现在默认移动,也就是说,当您定义新类型时,它不会实现,Copy
除非您为类型显式实现它:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
Run Code Online (Sandbox Code Playgroud)
只有当new struct
或者包含的每个类型enum
本身时,才能存在实现Copy
.如果没有,编译器将打印错误消息.它也可以只在类型存在不具有Drop
实现.
要回答你没有问过的问题......"移动和复制有什么用?":
首先,我将定义两个不同的"副本":
(&usize, u64)
,它在64位计算机上是16个字节,而浅拷贝将占用这16个字节并复制它们一些其他16字节的内存块中的值,而不触及usize
另一端的内存&
.也就是说,它等同于召唤memcpy
.Rc<T>
涉及仅增加引用计数,并且a的语义副本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
Run Code Online (Sandbox Code Playgroud)
它们是字节副本,无论是否T
移动或"可隐式复制".(要清楚,它们在运行时不一定是字面上的逐字节副本:如果保留代码的行为,编译器可以自由地优化副本.)
然而,字节副本存在一个基本问题:你最终会在内存中出现重复值,如果它们有析构函数,那么这可能非常糟糕,例如:
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Run Code Online (Sandbox Code Playgroud)
如果w
只是一个普通的字节副本,v
则会有两个向量指向相同的分配,两个都有析构函数释放它...导致双重释放,这是一个问题.NB.如果我们做了v
into 的语义副本w
,那w
将完全没问题,因为那时它将是它自己独立的,Vec<u8>
而析构函数不会相互踩踏.
这里有一些可能的修复:
w
有自己的分配,比如C++及其拷贝构造函数.v
不能再使用它并且不会运行析构函数.最后一个是Rust所做的:移动只是一个按值使用,其中源是静态无效的,因此编译器阻止进一步使用现在无效的内存.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Run Code Online (Sandbox Code Playgroud)
具有析构函数的类型必须在按值使用时移动(也就是在复制字节时),因为它们具有某些资源的管理/所有权(例如,内存分配或文件句柄),并且字节副本不太可能正确复制此所有权.
想想一个原始类型u8
:字节副本很简单,只需复制单个字节,语义副本就像复制单个字节一样简单.特别是,字节副本是一个语义副本...... Rust甚至有一个内置特征Copy
,可以捕获哪些类型具有相同的语义和字节副本.
因此,对于这些Copy
类型,按值使用也是自动语义副本,因此继续使用源是完全安全的.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Run Code Online (Sandbox Code Playgroud)
旧:
NoCopy
标记覆盖编译器的自动行为,即假设可以是Copy
(即仅包含基元的聚合和&
)的类型Copy
.但是,当实施选择加入的内置特征时,这将会改变.
如上所述,实现了opt-in内置特征,因此编译器不再具有自动行为.但是,过去用于自动行为的规则与检查实现是否合法的规则相同Copy
.
最简单的方法是在您的类型中嵌入不可复制的内容.
标准库为此用例提供了"标记类型":NoCopy.例如:
struct Triplet {
one: i32,
two: i32,
three: i32,
nocopy: NoCopy,
}
Run Code Online (Sandbox Code Playgroud)