对于以下Rust程序:
fn main() {
let foo = "test".to_string();
if false {
let _bar = foo; // value moved to _bar
}
println!("{}", foo);
}
运行时出现此错误:
error[E0382]: borrow of moved value: `foo`
--> src\main.rs:6:20
|
2 | let foo = "test".to_string();
| --- move occurs because `foo` has type `std::string::String`, which does not implement the `Copy` trait
3 | if false {
4 | let _bar = foo; // value moved to _bar
| --- value moved here
5 | }
6 | println!("{}", foo);
| ^^^ value borrowed here after move
有人可以帮忙解释一下这里发生了什么吗?我感到奇怪的是, move 发生在if语句中,而这永远是不正确的。另外,我想进一步了解这种情况,应该使用哪些关键字搜索?
答案 0 :(得分:5)
这是动作的秘密:它们并不真正存在。
移动不会生成与按位复制不同的代码(就机器代码而言)。¹移动和复制之间的唯一区别是“原始”内容会发生什么:如果仍然有效,则为复制;如果原件不再有效,则此举。
那么编译器如何强制您在移动后不使用原始值?没有运行时标志可以跟踪foo
是否有效。²而是,编译器使用源代码分析来确定在编译时foo
是否绝对有效或是否已被移出。尝试使用它的时间。由于此分析是在编译时进行的,因此它不会遵循函数内的执行流程;它同时针对整个功能发生。编译器发现foo
已移出if
内部,并拒绝以后使用foo
而不评估条件或任何代码。
智能编译器可以在进行有效性分析时将控制流纳入考虑范围,³但这可能不是一个改进。并非总是可能知道是否采用了分支(它是undecidable),因此在某些情况下编译器仍然会把它弄错。而且,正如Cerberus在问题注释中指出的那样,这将大大减慢编译器的通过速度。
采用另一种方式:在Rust中,您永远不会明确移动任何东西。您可以随便执行任何操作,然后让编译器根据类型是否为Copy
以及以后是否使用它来告诉您是否做错了。这与C ++不同,在C ++中,移动是可以调用“移动构造函数”并具有副作用的操作。在Rust中,这是一个完全静态的通过/失败检查。如果操作正确,程序将继续进行并进入下一个编译阶段。如果您做错了,借用检查器会告诉您(并希望可以帮助您解决问题)。
¹除非移动类型实现了Drop
,否则编译器可能会发出drop flags。
²实际上,存在 (放置标志),但是仅在放置foo
时才检查它,而不是每次使用时都检查。未实现Drop
的类型即使具有相同的移动语义也不会具有放置标志。
³这类似于Kotlin中的空检查工作原理:如果编译器可以确定引用绝对非空,则可以取消引用。 Rust中的有效性分析比这更保守。编译器甚至不会尝试猜测。