默认情况下,自定义类型通过默认赋值进行移动。通过实现Copy
特性,我可以通过默认赋值获得"浅层复制语义"。通过实现Clone
特性,我也可以获得"深层复制语义"。
是否有一种方法可以强制对Copy
类型进行移动?
我尝试使用move
关键字和闭包(let new_id = move || id;
),但是出现了错误消息。我还没有深入了解闭包,但是从这里和那里看到它们,我认为那应该行得通。
默认情况下,自定义类型通过默认赋值进行移动。通过实现Copy
特性,我可以通过默认赋值获得"浅层复制语义"。通过实现Clone
特性,我也可以获得"深层复制语义"。
是否有一种方法可以强制对Copy
类型进行移动?
我尝试使用move
关键字和闭包(let new_id = move || id;
),但是出现了错误消息。我还没有深入了解闭包,但是从这里和那里看到它们,我认为那应该行得通。
我不太理解你的问题,但是你似乎很困惑。所以我会解决这个困惑的根源:
我认为我正确地理解了C++中的复制/移动概念,但是“所有东西都是一个memcpy”并不直观,每次读到时都无法理解。
在思考 Rust 的移动语义时,请忽略 C++。C++的故事比Rust更复杂,而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
到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()
将此内容复制到 a
中:
b = { &mut data, length, capacity }
{ &mut copy, length, capacity } // temporary
a = { &mut copy, length, capacity }
由于 Vec
不是 Copy
类型,类型系统因此阻止了对临时变量的进一步访问。
但是效率呢?
到目前为止,我忽略了一件事,即移动和复制可以省略。Rust 保证某些平凡的移动和复制可以被省略。
由于编译器(在生命周期检查后)在两种情况下看到的结果相同,因此它们会以完全相同的方式被省略。
clone
经过一番试验后,现在似乎是所有这些神奇的东西中最重要的部分。我尝试为本地类型实现它,就像 这里 中所实现的那样,但它会出错,因为我正在尝试通过值传递一个非复制类型,而我只是借用它。那么,对于任何复杂类型,克隆可能发生在哪里呢?!(注意:这不是我的设置中的错误;#[derive(...)]
可以正常工作。) - NoeinClone
实现只是重定向到 Copy
。如果你的类型不可复制,你需要更聪明一些。例如,#[derive(clone)]
将会 Clone
每个结构体成员。 - VeedracCopy
依赖于Clone
。 - NoeinCopy
需要Clone
,因为没有理由只实现Copy
。如果有人想要接受T: Clone
,那么如果他们不能接受Copy
类型,那就太傻了。编译器错误只是为了防止人们忘记。 - Veedrac将可复制类型包装在另一种不实现Copy
的类型中。
struct Noncopyable<T>(T);
fn main() {
let v0 = Noncopyable(1);
let v1 = v0;
println!("{}", v0.0); // error: use of moved value: `v0.0`
}
新回答
有时候我希望程序能够“大喊”:“在这里放入一个新的值!”
那么答案就是“不行”。当移动实现了Copy
的类型时,源和目标始终都是有效的。当移动未实现Copy
的类型时,源将永远无效,而目标始终有效。没有语法或特性可以表示“让我选择此实现了Copy
的类型是否在此时充当Copy
”。
原始回答
我只是想有时候说:“是的,这个类型是可复制的,但我真的不再需要这个变量中的值了。这个函数按值接受参数,就直接拿去吧。”
看起来你正在手动完成优化器的工作。不用担心,优化器会为你完成这项工作。这样做的好处是不需要担心它。
Copy
和!Copy
底层的技术区别(即“所有东西都是memcpy;有时是浅复制,有时是深复制,但几乎没有直觉。”)。 - NoeinCopy
特质存在的原因之一是对于某些类型,如普通整数或布尔值,留下不可用的东西是没有意义的。虽然你可能无法通过查看代码行来判断,但编译器会迅速提供错误提示,指出你正在尝试使用已移动的非 Copy
值,因此你不必自己思考。 - Shepmaster= move
赋值或类似的内容。let coord = (99.9, 73.45);
let mut coord2 = move coord;
coord2.0 += 100.0;
println!("coord2 = {:?}", coord2);
println!("coord = {:?}", coord); // Error
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
仍然有效,所以这完全没有帮助。
Copy
,那么 Clone
应该始终委托给 Copy
实现。没有什么好的理由让 Clone
做与 Copy
不同的事情。 - Shepmasterprintln!
调用呢?这就是我所说的“副作用”。 - Earth EngineCopy
类型时,总是希望有值语义,而在不使用Copy
类型时,则希望有对象语义。
Copy
,但我不再需要这个变量中的值了。 这个函数通过val参数传递,直接拿走它。” 然后,在调用函数或其他操作之后,尝试重复使用标识符而不给它另一个值将在编译时产生错误。就是这样。 - Noein