如何强制移动实现 Copy 特征的类型? [英] How to force a move of a type which implements the Copy trait?

查看:61
本文介绍了如何强制移动实现 Copy 特征的类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

默认情况下,自定义类型通过默认分配移动.通过实现 Copy 特性,我通过默认赋值获得了浅拷贝语义".我也可以通过实现 Clone trait 来获得深度复制语义".

有没有办法在 Copy 类型上强制 move ?

我尝试使用 move 关键字和闭包 (let new_id = move || id;) 但我收到一条错误消息.我还没有关闭,但是,从这里和那里看到它们,我认为这会奏效.

解决方案

我不太明白你的问题,但你肯定看起来很困惑.所以我将解决似乎是这种混乱的根源:

<块引用>

复制/移动的 C++ 概念我认为我理解正确,但是这个无论如何都是 memcpy"是,好吧,每次我读它时都不是很直观

在考虑 Rust 的移动语义时,忽略 C++.C++ 的故事比 Rust 的复杂得多,后者非常简单.然而,用 C++ 来解释 Rust 的语义是一团糟.

<块引用>

TL;DR:复制移动.移动副本.只有类型检查器知道区别.因此,当您想为 Copy 类型强制移动"时,您要求的是您已经拥有的东西.

所以我们有三个语义:

  • let a = b where b is not Copy
  • let a = b where b is Copy
  • let a = b.clone() 其中 bClone
<块引用>

注意:赋值和初始化之间没有有意义的区别(就像在 C++ 中一样)——赋值只是首先删除旧值.

<块引用>

注意:函数调用参数的工作方式与赋值类似.f(b)b 赋值给 f 的参数.

<小时>

第一件事.

a = b 总是 执行 memcpy.

所有三种情况中都是如此.

  • 当你执行 let a = b 时,bmemcpy 转化为 a.
  • 当你做let a = b.clone()时,b.clone()的结果是memcpy'd into <代码>a.
<小时>

移动

想象一下 b 是一个 Vec.Vec 看起来像这样:

{ &mut 数据,长度,容量}

当你写 let a = b 时,你最终得到:

b = { &mut 数据,长度,容量}a = { &mut 数据,长度,容量}

这意味着 ab 都引用了 &mut data,这意味着我们有别名可变数据.

类型系统不喜欢这样,所以说我们不能再次使用 b.任何对 b 的访问都将在编译时失败.

<块引用>

注意: ab 不必为堆数据添加别名,这使得两者都使用是个坏主意.例如,它们都可以是文件句柄 - 一个副本会导致文件被关闭两次.

<块引用>

注意:当涉及析构函数时,移动确实有额外的语义,但编译器不会让你在带有析构函数的类型上编写Copy.

<小时>

副本

想象一下 b 是一个 Option.Option 看起来像这样:

{ is_valid, data }

当你写 let a = b 时,你最终得到:

b = { is_valid, data }a = { is_valid, 数据 }

它们可以同时使用.为了告诉类型系统这是这种情况,将 Option 标记为 Copy.

<块引用>

注意:标记某些副本不会改变代码的作用.它只允许更多的代码.如果你删除一个Copy 实现,你的代码要么出错,要么做完全同样的事情.同样,将非Copy 类型标记为Copy 不会更改任何已编译的代码.

<小时>

克隆

假设您想复制一个 Vec,然后.你实现了 Clone,它产生了一个 new Vec,然后做

让 a = b.clone()

这执行两个步骤.我们从:

b = { &mut 数据,长度,容量}

运行 b.clone() 为我们提供了一个额外的临时右值

b = { &mut 数据,长度,容量}{ &mut copy, length, capacity }//临时

运行 let a = b.clone() memcpys 到 a:

b = { &mut 数据,长度,容量}{ &mut copy, length, capacity }//临时a = { &mut 复制,长度,容量 }

因此类型系统阻止了对临时文件的进一步访问,因为 Vec 不是 Copy.

<小时>

但是效率呢?

到目前为止我跳过的一件事是可以省略移动和副本.Rust 保证某些微不足道的移动和副本被忽略.

因为编译器(在生命周期检查之后)在两种情况下看到相同的结果,所以它们以完全相同的方式被省略.

A custom type by default is moved through default assignment. By implementing the Copy trait, I get "shallow copy semantics" through default assignment. I may also get "deep copy semantics" by implementing the Clone trait.

Is there a way to force a move on a Copy type?

I tried using the move keyword and a closure (let new_id = move || id;) but I get an error message. I'm not into closures yet, but, from seeing them here and there, I thought that that would have worked.

解决方案

I don't really understand your question, but you certainly seem confused. So I'll address what seems to be the root of this confusion:

The C++ notions of copy/move I think I get correctly, but this 'everything is a memcpy anyway' is, well, it hasn't been very intuitive any time I read it

When thinking about Rust's move semantics, ignore C++. The C++ story is way more complicated than Rust's, which is remarkably simple. However, explaining Rust's semantics in terms of C++ is a mess.

TL;DR: Copies are moves. Moves are copies. Only the type checker knows the difference. So when you want to "force a move" for a Copy type, you are asking for something you already have.

So we have three semantics:

  • let a = b where b is not Copy
  • let a = b where b is Copy
  • let a = b.clone() where b is Clone

Note: There is no meaningful difference between assignment and initialization (like in C++) - assignment just first drops the old value.

Note: Function call arguments work just like assignment. f(b) assigns b to the argument of f.


First things first.

The a = b always performs a memcpy.

This is true in all three cases.

  • When you do let a = b, b is memcpy'd into a.
  • When you do let a = b.clone(), the result of b.clone() is memcpy'd into a.

Moves

Imagine b was a Vec. A Vec looks like this:

{ &mut data, length, capacity }

When you write let a = b you thus end up with:

b = { &mut data, length, capacity }
a = { &mut data, length, capacity }

This means that a and b both reference &mut data, which means we have aliased mutable data.

The type-system doesn't like this so says we can't use b again. Any access to b will fail at compile-time.

Note: a and b don't have to alias heap data to make using both a bad idea. For example, they could both be file handles - a copy would result in the file being closed twice.

Note: Moves do have extra semantics when destructors are involved, but the compiler won't let you write Copy on types with destructors anyway.


Copies

Imagine b was an Option<i32>. An Option<i32> looks like this:

{ is_valid, data }

When you write let a = b you thus end up with:

b = { is_valid, data }
a = { is_valid, data }

These are both usable simultaneously. To tell the type system that this is the case, one marks Option<i32> as Copy.

Note: Marking something copy doesn't change what the code does. It only allows more code. If you remove a Copy implementation, your code will either error or do exactly the same thing. In the same vein, marking a non-Copy type as Copy will not change any compiled code.


Clones

Imagine you want to copy a Vec, then. You implement Clone, which produces a new Vec, and do

let a = b.clone()

This performs two steps. We start with:

b = { &mut data, length, capacity }

Running b.clone() gives us an additional rvalue temporary

b = { &mut data, length, capacity }
    { &mut copy, length, capacity } // temporary

Running let a = b.clone() memcpys this into a:

b = { &mut data, length, capacity }
    { &mut copy, length, capacity } // temporary
a = { &mut copy, length, capacity }

Further access of the temporary is thus prevented by the type system, since Vec is not Copy.


But what about efficiency?

One thing I skipped over so far is that moves and copies can be elided. Rust guarantees certain trivial moves and copies to be elided.

Because the compiler (after lifetime checking) sees the same result in both cases, these are elided in exactly the same way.

这篇关于如何强制移动实现 Copy 特征的类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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