是否可以使类型只能移动而不能复制? [英] Is it possible to make a type only movable and not copyable?

查看:50
本文介绍了是否可以使类型只能移动而不能复制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

<块引用>

编者注:这个问题是在 Rust 1.0 之前提出的,问题中的一些断言在 Rust 1.0 中不一定正确.一些答案已更新以解决这两个版本.

我有这个结构

struct Triplet {一:i32,二:i32,三:i32,}

如果我将它传递给一个函数,它会被隐式复制.现在,有时我读到某些值不可复制,因此必须移动.

是否可以使这个结构 Triplet 不可复制?例如,是否有可能实现一个特性,使 Triplet 不可复制并因此可移动"?

我在某处读到有人必须实现 Clone 特性来复制不可隐式复制的东西,但我从未读过相反的东西,即具有隐式可复制的东西并制作它是不可复制的,因此它会移动.

这还有意义吗?

解决方案

前言:这个回答写在 选择内置特性—特别是复制方面—已实施.我使用块引号来指示仅适用于旧方案的部分(提出问题时适用的方案).

<小时><块引用>

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

struct Triplet {一:int,二:int,三:int,_marker:NoCopy}

您也可以通过使用析构函数(通过实现 Drop trait),但如果析构函数什么都不做,最好使用标记类型.

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

struct Triplet {一:i32,二:i32,三:i32}impl Copy for Triplet {}//添加此用于复制,将其保留以用于移动

只有当新的 structenum 中包含的每个类型本身都是 Copy 时,实现才能存在.如果没有,编译器将打印错误消息.它也只能在类型没有具有 Drop 实现时才存在.

<小时>

回答你没有问过的问题......移动和复制怎么了?":

首先,我将定义两个不同的副本":

  • 一个字节复制,它只是逐字节浅层复制对象,而不是跟随指针,例如如果你有 (&usize, u64),它在 64 位计算机上是 16 个字节,浅拷贝将获取这 16 个字节并在其他一些 16 字节块中复制它们的值内存,无需触及 & 另一端的 usize.也就是说,它相当于调用了memcpy.
  • 一个语义副本,复制一个值来创建一个新的(有点)独立的实例,可以安全地与旧实例分开使用.例如.Rc 的语义副本仅涉及增加引用计数,而 Vec 的语义副本涉及创建新分配,然后语义复制每个存储的元素从旧到新.这些可以是深拷贝(例如 Vec)或浅拷贝(例如 Rc 不接触存储的 T),Clone 松散地定义为将 T 类型的值从 &T 内部语义复制到 所需的最小工作量.

Rust 就像 C,每次按值使用一个值都是一个字节副本:

let x: T = ...;让 y: T = x;//字节拷贝fn foo(z: T) ->T{return z//字节拷贝}foo(y)//字节复制

无论 T 是否移动或隐式可复制",它们都是字节副本.(需要明确的是,它们在运行时不一定是逐字节的副本:如果保留代码的行为,编译器可以自由地优化副本.)

然而,字节复制有一个基本问题:你最终会在内存中得到重复的值,如果它们有析构函数,这可能会非常糟糕,例如

{让 v:Vec u8 ;= vec![1, 2, 3];让w:Vec u8 ;= v;}//析构函数在这里运行

如果 w 只是 v 的普通字节副本,那么将有两个向量指向同一个分配,两个向量都带有释放它的析构函数......导致 双重免费,这是一个问题.注意.如果我们将 v 的语义复制到 w 中,这将非常好,因为这样 w 将是它自己独立的 Vec<u8> 和析构函数不会互相践踏.

这里有一些可能的修复:

  • 让程序员像 C 一样处理它.(C 中没有析构函数,所以它并没有那么糟糕......你只会留下内存泄漏.:P)
  • 隐式执行语义复制,以便 w 拥有自己的分配,就像 C++ 及其复制构造函数一样.
  • 将按值使用视为所有权的转移,因此 v 不能再被使用,也不会运行其析构函数.

最后一个是 Rust 所做的:move 只是按值使用,其中源静态无效,因此编译器防止进一步使用现在无效的内存.

let v: Vec= vec![1, 2, 3];让w:Vec u8 ;= v;println!("{}", v);//错误:使用移动值

具有析构函数的类型必须在按值使用时(即字节复制时)移动,因为它们对某些资源(例如内存分配或文件句柄)及其字节副本不太可能正确复制此所有权.

嗯……什么是隐式副本?"

想想像u8这样的原始类型:字节拷贝很简单,只拷贝单个字节,语义拷贝也同样简单,拷贝单个字节.特别是,字节副本语义副本...... Rust 甚至有一个 内置特征 Copy 捕获哪些类型具有相同的语义和字节副本.

因此,对于这些 Copy 类型,按值使用也是自动语义复制,因此继续使用源代码是完全安全的.

let v: u8 = 1;让 w: u8 = v;println!("{}", v);//完全没问题

<块引用>

:NoCopy 标记会覆盖编译器的自动行为,即假定可以Copy 的类型(即仅包含基元和&) 是 Copy.然而,当 选择内置特征已实现.

如上所述,实现了可选的内置特征,因此编译器不再具有自动行为.但是,过去用于自动行为的规则与检查实现Copy是否合法的规则相同.

Editor's note: this question was asked before Rust 1.0 and some of the assertions in the question are not necessarily true in Rust 1.0. Some answers have been updated to address both versions.

I have this struct

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

If I pass this to a function, it is implicitly copied. Now, sometimes I read that some values are not copyable and therefore have to moved.

Would it be possible to make this struct Triplet non-copyable? For example, would it be possible to implement a trait which would make Triplet non-copyable and therefore "movable"?

I read somewhere that one has to implement the Clone trait to copy things that are not implicitly copyable, but I never read about the other way around, that is having something that is implicitly copyable and making it non-copyable so that it moves instead.

Does that even make any sense?

解决方案

Preface: This answer was written before opt-in built-in traits—specifically the Copy aspects—were implemented. I've used block quotes to indicate the sections that only applied to the old scheme (the one that applied when the question was asked).


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.

Types now move by default, that is, when you define a new type it doesn't implement Copy unless you explicitly implement it for your type:

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

The implementation can only exist if every type contained in the new struct or enum is itself Copy. If not, the compiler will print an error message. It can also only exist if the type doesn't have a Drop implementation.


To answer the question you didn't ask... "what's up with moves and copy?":

Firstly I'll define two different "copies":

  • a byte copy, which is just shallowly copying an object byte-by-byte, not following pointers, e.g. if you have (&usize, u64), it is 16 bytes on a 64-bit computer, and a shallow copy would be taking those 16 bytes and replicating their value in some other 16-byte chunk of memory, without touching the usize at the other end of the &. That is, it's equivalent to calling memcpy.
  • a semantic copy, duplicating a value to create a new (somewhat) independent instance that can be safely used separately to the old one. E.g. a semantic copy of an Rc<T> involves just increasing the reference count, and a semantic copy of a Vec<T> involves creating a new allocation, and then semantically copying each stored element from the old to the new. These can be deep copies (e.g. Vec<T>) or shallow (e.g. Rc<T> doesn't touch the stored T), Clone is loosely defined as the smallest amount of work required to semantically copy a value of type T from inside a &T to T.

Rust is like C, every by-value use of a value is a byte copy:

let x: T = ...;
let y: T = x; // byte copy

fn foo(z: T) -> T {
    return z // byte copy
}

foo(y) // byte copy

They are byte copies whether or not T moves or is "implicitly copyable". (To be clear, they aren't necessarily literally byte-by-byte copies at run-time: the compiler is free to optimise the copies out if code's behaviour is preserved.)

However, there's a fundamental problem with byte copies: you end up with duplicated values in memory, which can be very bad if they have destructors, e.g.

{
    let v: Vec<u8> = vec![1, 2, 3];
    let w: Vec<u8> = v;
} // destructors run here

If w was just a plain byte copy of v then there would be two vectors pointing at the same allocation, both with destructors that free it... causing a double free, which is a problem. NB. This would be perfectly fine, if we did a semantic copy of v into w, since then w would be its own independent Vec<u8> and destructors wouldn't be trampling on each other.

There's a few possible fixes here:

  • Let the programmer handle it, like C. (there's no destructors in C, so it's not as bad... you just get left with memory leaks instead. :P )
  • Perform a semantic copy implicitly, so that w has its own allocation, like C++ with its copy constructors.
  • Regard by-value uses as a transfer of ownership, so that v can no longer be used and doesn't have its destructor run.

The last is what Rust does: a move is just a by-value use where the source is statically invalidated, so the compiler prevents further use of the now-invalid memory.

let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value

Types that have destructors must move when used by-value (aka when byte copied), since they have management/ownership of some resource (e.g. a memory allocation, or a file handle) and its very unlikely that a byte copy will correctly duplicate this ownership.

"Well... what's an implicit copy?"

Think about a primitive type like u8: a byte copy is simple, just copy the single byte, and a semantic copy is just as simple, copy the single byte. In particular, a byte copy is a semantic copy... Rust even has a built-in trait Copy that captures which types have identical semantic and byte copies.

Hence, for these Copy types by-value uses are automatically semantic copies too, and so it's perfectly safe to continue using the source.

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

Old: The NoCopy marker overrides the compiler's automatic behaviour of assuming that types which can be Copy (i.e. only containing aggregates of primitives and &) are Copy. However this will be changing when opt-in built-in traits is implemented.

As mentioned above, opt-in built-in traits are implemented, so the compiler no longer has automatic behaviour. However, the rule used for the automatic behaviour in the past are the same rules for checking whether it is legal to implement Copy.

这篇关于是否可以使类型只能移动而不能复制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆