移动与铁锈复制

比方说,我有这个结构

struct Triplet { one: i32, two: i32, three: i32, } 

如果我把它传递给一个函数,它将被隐式复制 。 现在有时我读到有些值是不可复制的 ,因此不得不移动 。 我想知道,是否有可能使这个struct Triplet不可复制 ? 像实施一些额外的特点,告诉生锈,这将是不可复制的 ,因此必须移动

我到处读到,人们必须实现Clone特质才能复制那些不能隐式复制的东西,但是我从来没有读过其他方法,那就是隐式复制 ,使其不可复制 ,而不是复制

这甚至有什么意义吗?

前言 :这个答案是在select内置特性之前编写的,特别是Copy方面 – 已经实现。 我使用了块引用来表示只适用于旧scheme的部分(在提问时应用的部分)。


:要回答基本问题,可以添加一个存储NoCopy值的标记字段。 例如

 struct Triplet { one: int, two: int, three: int, _marker: NoCopy } 

你也可以通过析构函数来实现(通过实现Drop特性 ),但是如果析构函数什么都不做,那么使用标记types是首选。

types现在默认移动,也就是说,当你定义一个新的types时,它不会实现Copy除非你明确地为你的types实现:

 struct Triplet { one: i32, two: i32, three: i32 } impl Copy for Triplet {} // add this for copy, leave it out for move 

只有在新的structenum包含的每个types都是自己的Copy才能实现该实现。 如果没有,编译器将打印一条错误消息。 它也可以只存在,如果types没有一个Drop实现。


要回答你没有问的问题……“怎么了移动和复制?”:

首先我要定义两个不同的“副本”:

  • 一个字节拷贝 ,它只是逐字节拷贝一个对象,不跟随指针,例如,如果你有(&usize, u64) ,在64位计算机上它是16个字节,浅拷贝将会把这些拷贝16字节并在其他一些16字节的内存块中复制它们的值, 而不触及&的另一端的usize 。 也就是说,这相当于调用memcpy
  • 一个语义副本 ,复制一个值来创build一个新的(有些)独立的实例,可以安全地单独使用旧的实例。 例如,一个Rc<T>的语义拷贝只涉及增加引用计数,而一个Vec<T>的语义拷贝包括创build一个新的分配,然后将每个存储的元素从旧的语义复制到新的。 这些可以是深层复制(例如Vec<T> )或浅层(例如Rc<T>不接触存储的T ), Clone被松散地定义为从内部语义复制typesT的值所需的最小量的工作a &T to 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 ,这是一个问题。 NB。 如果我们把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 

因为他们有一些资源的pipe理/所有权(例如内存分配或文件句柄),并且字节拷贝不太可能正确地复制这个值,所以具有析构函数的types必须在使用值时移动(也就是当字节复制时)所有权。

“那么…什么是隐含的副本?”

考虑一下像u8这样的基本types:一个字节拷贝很简单,就是复制单个字节,而一个语义拷贝就是一样简单,拷贝一个字节。 特别是,一个字节拷贝一个语义拷贝… Rust甚至有一个内置的特征Copy ,捕获哪些types具有相同的语义和字节拷贝。

因此,对于这些Copytypes,按值使用也是自动语义副本,所以继续使用源是完全安全的。

 let v: u8 = 1; let w: u8 = v; println!("{}", v); // perfectly fine 

OldNoCopy标记覆盖了编译器的自动行为,假定可以是Copytypes(即只包含原语和&聚合)是Copy 。 但是,如果select内置特征,这将会改变。

如上所述,select性内置特征被实现,所以编译器不再具有自动行为。 但是,过去用于自动行为的规则与检查Copy是否合法相同。

最简单的方法是embedded你的types不可复制的东西。

标准库为这个用例提供了一个“标记types”: NoCopy 。 例如:

 struct Triplet { one: i32, two: i32, three: i32, nocopy: NoCopy, }