默认情况下,自定义类型将通过默认分配移动.通过实现Copy特性,我通过默认赋值获得"浅拷贝语义".我也可以通过实现Clone特征来获得"深度复制语义" .
有没有办法强制搬迁在Copy类型?
我尝试使用move关键字和一个closure(let new_id = move || id;),但我收到一条错误消息.我还没有关闭,但是,从这里和那里看到它们,我认为那会有效.
Vee*_*rac 22
我真的不明白你的问题,但你肯定感到困惑.所以我将解决这个混乱的根源:
C++关于复制/移动的概念我认为我得到了正确的答案,但是"无论如何,这一切都是一个记忆",嗯,任何时候我读它都不是很直观
在考虑Rust的移动语义时,忽略C++.C++故事比Rust更复杂,非常简单.但是,用C++解释Rust的语义是一团糟.
TL; DR:副本是移动.移动是副本.只有类型检查器知道差异.因此,当你想要为一种
Copy类型"强制移动"时,你会要求你已经拥有的东西.
所以我们有三个语义:
let a = b哪里b 不是 Copylet a = b这里b 是 Copylet 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'进入a.移动
想象一下,b是一个Vec.A Vec看起来像这样:
{ &mut data, length, capacity }
Run Code Online (Sandbox Code Playgroud)
当你写信let a = b给你时,你最终得到:
b = { &mut data, length, capacity }
a = { &mut data, length, capacity }
Run Code Online (Sandbox Code Playgroud)
这意味着a和b两个引用&mut data,这意味着我们有别名的可变数据.
类型系统不喜欢这样,所以说我们不能再使用b了.任何访问都b将在编译时失败.
注意:
a并且b不必使用别名堆数据来使用这两个坏主意.例如,它们都可以是文件句柄 - 副本会导致文件被关闭两次.
注意:当涉及析构函数时,移动确实有额外的语义,但编译器不会让你
Copy用反构函数写入带有析构函数的类型.
副本
想象一下,b是一个Option<i32>.一个Option<i32>看起来是这样的:
{ is_valid, data }
Run Code Online (Sandbox Code Playgroud)
当你写信let a = b给你时,你最终得到:
b = { is_valid, data }
a = { is_valid, data }
Run Code Online (Sandbox Code Playgroud)
这些都可以同时使用.为了告诉类型系统这种情况,标记Option<i32>为Copy.
注意:标记副本不会改变代码的作用.它只允许更多的代码.如果删除
Copy实现,则代码将出错或执行完全相同的操作.同样,将非Copy类型标记为Copy不会更改任何已编译的代码.
克隆
想象一下,你想复制一个Vec,然后.你实现Clone,产生一个新的 Vec,并做
let a = b.clone()
Run Code Online (Sandbox Code Playgroud)
这执行两个步骤.我们从:
b = { &mut data, length, capacity }
Run Code Online (Sandbox Code Playgroud)
跑步b.clone()给我们额外的右值临时值
b = { &mut data, length, capacity }
{ &mut copy, length, capacity } // temporary
Run Code Online (Sandbox Code Playgroud)
let a = b.clone() memcpy将此运行到a:
b = { &mut data, length, capacity }
{ &mut copy, length, capacity } // temporary
a = { &mut copy, length, capacity }
Run Code Online (Sandbox Code Playgroud)
因此,类型系统Vec不会进一步访问临时访问Copy.
但效率怎么样?
到目前为止,我跳过的一件事是可以省略移动和复制.Rust 保证某些琐碎的动作和副本被省略.
因为编译器(在生命周期检查之后)在两种情况下看到相同的结果,所以这些都以完全相同的方式被省略.
将可复制类型换行为另一种未实现的类型Copy.
struct Noncopyable<T>(T);
fn main() {
let v0 = Noncopyable(1);
let v1 = v0;
println!("{}", v0.0); // error: use of moved value: `v0.0`
}
Run Code Online (Sandbox Code Playgroud)
移动和复制本质上是相同的运行时操作。编译器插入代码以将第一个变量的地址按位复制到第二个变量的地址。在移动的情况下,编译器还会使第一个变量无效,因此如果随后使用它,则会出现编译错误。
即便如此,我认为如果 Rust 语言允许程序说该赋值是显式移动而不是副本,那么仍然有效。它可以通过防止无意中引用错误的实例来捕获错误。如果编译器知道您不需要两个副本并且可以调整绑定以避免按位复制,那么在某些情况下它还可能生成更有效的代码。
例如,您是否可以陈述= move作业或类似内容。
let coord = (99.9, 73.45);
let mut coord2 = move coord;
coord2.0 += 100.0;
println!("coord2 = {:?}", coord2);
println!("coord = {:?}", coord); // Error
Run Code Online (Sandbox Code Playgroud)
新答案
有时我只是想让它对我尖叫“在这里放一个新值!”。
那么答案是“不”。移动实现 的类型时,Copy源和目标都将始终有效。移动未实现的类型时,Copy源将永远不会有效,而目标将始终有效。没有语法或特征意味着“让我选择这个实现的类型此时是否Copy起作用Copy”。
原答案
有时我只是想说“是的,这种类型是 Copy,但我真的不再需要这个变量中的这个值了。这个函数通过 val 接受一个 arg,只需接受它。”
听起来您正在尝试手动完成优化器的工作。别担心,优化器会为你做这件事。这样做的好处是无需担心。