默认情况下,自定义类型已移动通过默认分配。通过实现Copy
特征,我通过默认赋值获得“浅拷贝语义”。我也可以通过实现Clone
特征来获得“深度复制语义”。
有没有办法在Copy
类型上强制移动?
我尝试使用move
关键字和闭包(let new_id = move || id;
),但收到错误消息。我还没有关闭,但是,从这里和那里看到它们,我认为那会有效。
答案 0 :(得分:21)
我真的不明白你的问题,但你肯定感到困惑。因此,我将解决这种混乱的根源:
C ++关于复制/移动的概念我认为我得到了正确的答案,但这一切都是一个memcpy无论如何'是的,在我阅读它的时候,它并没有非常直观
在考虑Rust的移动语义时,忽略C ++ 。 C ++故事比Rust更复杂,非常简单。但是,用C ++解释Rust的语义是一团糟。
TL; DR:副本 移动。移动是副本。只有类型检查器知道差异。所以,当你想要强行采取行动时,对于
Copy
类型,您要求的是已有的东西。
所以我们有三种语义:
let a = b
其中b
不是 Copy
let a = b
其中b
Copy
let a = b.clone()
其中b
为Clone
注意:分配和初始化之间没有任何有意义的区别(比如在C ++中) - 首先分配
drop
旧值。
注意:函数调用参数就像赋值一样工作。
f(b)
将b
分配给f
的参数。
首先要做的事情。
a = b
总是执行memcpy
。
所有三种情况都是如此。
let a = b
后,b
memcpy
进入a
。let a = b.clone()
时,b.clone()
的结果为memcpy
&{39}进入a
。<强>移至强>
想象一下b
是Vec
。 Vec
看起来像这样:
{ &mut data, length, capacity }
当你写let a = b
时,你最终得到了:
b = { &mut data, length, capacity }
a = { &mut data, length, capacity }
这意味着a
和b
都引用&mut data
,这意味着我们有别名可变数据。
类型系统不是这样的,所以说我们不能再次使用b
。对b
的任何访问都将在编译时失败。
注意:
a
和b
不必使用堆叠数据来使用这两个错误的想法。例如,它们都可以是文件句柄 - 副本会导致文件被关闭两次。
注意:当涉及析构函数时,移动确实有额外的语义,但编译器不会让你在带有析构函数的类型上写
Copy
。
<强>份数强>
想象一下b
是Option<i32>
。 Option<i32>
看起来像这样:
{ is_valid, data }
当你写let a = b
时,你最终得到了:
b = { is_valid, data }
a = { is_valid, data }
这些都可以同时使用。要告诉类型系统这种情况,可以将Option<i32>
标记为Copy
。
注意:标记某些副本并不会改变代码的作用。它只允许更多的代码。如果您删除了
Copy
实现,那么您的代码将会出错,或者完全同样的事情。同样,将非Copy
类型标记为Copy
不会更改任何已编译的代码。
<强>克隆强>
想象一下,你想复制Vec
,然后。您实施Clone
,生成新 Vec
,并执行
let a = b.clone()
这执行两个步骤。我们从:
开始b = { &mut data, length, capacity }
运行b.clone()
为我们提供了额外的右值临时值
b = { &mut data, length, capacity }
{ &mut copy, length, capacity } // temporary
将let a = b.clone()
memcpy
运行到a
:
b = { &mut data, length, capacity }
{ &mut copy, length, capacity } // temporary
a = { &mut copy, length, capacity }
类型系统因此阻止了临时访问,因为Vec
不是Copy
。
但效率怎么样?
到目前为止我跳过的一件事是可以省略动作和副本。 Rust 保证某些琐碎的动作和副本被省略。
因为编译器(在终身检查之后)在两种情况下看到相同的结果,所以这些都以完全相同的方式被省略。
答案 1 :(得分:7)
将可复制类型换行为另一种不会实现Copy
的类型。
struct Noncopyable<T>(T);
fn main() {
let v0 = Noncopyable(1);
let v1 = v0;
println!("{}", v0.0); // error: use of moved value: `v0.0`
}
答案 2 :(得分:4)
新答案
有时我只是想让它对我尖叫,并在这里添加一个新值!&#34;。
然后答案是&#34;没有&#34;。移动实现Copy
的类型时,源和目标都将始终有效。移动未实现Copy
的类型时,源将永远无效且目标始终有效。没有语法或特征意味着&#34;让我选择实现Copy
的此类型此时是Copy
&#34;。
原始答案
我只是想有时候说&#34;是的,这种类型是复制,但我真的不再需要这个变量中的这个值了。这个函数采用arg by val,只需要它。&#34;
听起来你正试图手工完成优化器的工作。不要担心,优化器会为您做到这一点。这样做的好处是无需担心。
答案 3 :(得分:1)
移动和复制基本上是在后台进行相同的运行时操作。编译器插入代码以从第一个变量的地址向第二个变量的地址进行按位复制。如果发生移动,编译器还会使第一个变量无效,这样,如果随后使用该变量,将导致编译错误。
即使如此,我认为,如果Rust语言允许程序说任务是显式移动而不是复制,那么仍然有效。它可以通过防止无意中引用错误的实例来捕获错误。如果编译器知道您不需要两个副本,并且可以微调绑定以避免按位复制,则在某些情况下它也可以生成更有效的代码。
例如如果您可以声明= move
分配或类似分配。
let coord = (99.9, 73.45);
let mut coord2 = move coord;
coord2.0 += 100.0;
println!("coord2 = {:?}", coord2);
println!("coord = {:?}", coord); // Error
答案 4 :(得分:0)
在运行时,副本和移动在Rust中具有相同的效果。但是,在编译时,在 move 的情况下,对象从移出的变量被标记为不可用,但不是 >复制
当您使用Copy
类型时,在不使用Copy
类型时,您总是需要值语义和对象语义。
Rust中的对象没有一致的地址:由于运行时行为,地址通常会在移动之间发生变化,即它们只由一个绑定所拥有。这与其他语言非常不同!
答案 5 :(得分:0)
在Rust中,当您使用(或移动,用Rust的术语来说)值为Copy
时,原始值仍然有效。如果要模拟与其他不可复制的值一样在特定用途后失效的情况,则可以执行以下操作:
let v = 42i32;
// ...
let m = v;
// redefine v such that v is no longer a valid (initialized) variable afterwards
// Unfortunately you have to write a type here. () is the easiest,
// but can be used unintentionally.
let v: ();
// If the ! type was stabilized, you can write
let v: !;
// otherwise, you can define your own:
enum NeverType {};
let v: NeverType;
// ...
如果以后将v
更改为非Copy
的内容,则不必更改上面的代码,以避免使用移动的值。
更正了对该问题的一些误解
Clone
和Copy
之间的区别不是“浅拷贝”和“深拷贝”语义。 Copy
是“ memcpy”语义,Clone
是实现者喜欢的东西,这是唯一的区别。尽管根据定义,需要“深度复制”的内容无法实现Copy
。
当一个类型同时实现Copy
和Clone
时,除了Clone
可能会有副作用之外,期望它们都具有相同的语义。对于实现Copy
的类型,其Clone
不应具有“深度复制”语义,并且复制的结果应与复制的结果相同。
作为尝试,如果您想使用闭包来提供帮助,则可能想要运行闭包,例如let new_id = (move || id)();
。如果复制了id
,那么移动后id
仍然有效,所以这根本没有帮助。