对于以下 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
谁能帮忙解释一下这里发生了什么?令我感到奇怪的是,这一举动发生在永远不会为真的 if 语句中。另外我想了解更多关于这种情况,我应该使用哪些关键字来搜索?
最佳答案
这是移动的秘诀:它们实际上并不存在。
移动不会生成与按位复制不同的代码(就机器代码而言)。¹ 移动和复制之间的唯一区别在于“原始”发生了什么:如果它仍然有效,则它是一个副本;如果原件不再有效,那就是一个举动。
那么编译器如何强制您在移动后不使用原始值呢?没有运行时标志来跟踪 foo
是否有效。² 相反,编译器在编译时使用源代码分析来确定 foo
是否有效。绝对有效,或者在您尝试使用它时可能已被移出。因为这种分析发生在编译时,它不遵循函数内的执行流程;它立即发生在整个函数中。编译器看到 foo
被移出 if
,并拒绝以后使用 foo
不评估条件或任何代码。
智能编译器可以在进行有效性分析时考虑控制流,³ 但这可能不是改进。并不总是可以知道是否采用了分支(它是 undecidable ),因此在某些情况下编译器仍然会出错。此外,正如 Cerberus 在问题评论中指出的那样,它会大大减慢编译器的速度。
换句话说:在 Rust 中,你永远不会明确移动某些东西。你想怎么用就怎么用,让编译器根据类型是不是Copy
来告诉你是不是做错了。以及以后是否使用。这与 C++ 不同,在 C++ 中,移动是一种可能调用“移动构造函数”并具有副作用的操作;在 Rust 中,它是一个纯静态的通过/失败检查。如果你做对了,程序就会通过并进入下一个编译阶段;如果你做错了,借阅检查员会告诉你(并希望能帮助你解决它)。
也可以看看
¹ 除非移动类型实现
Drop
,在这种情况下,编译器可能会发出 drop flags .² 其实是有(drop flag),但是只有在
foo
时才检查被丢弃,而不是在每次使用时。未实现的类型 Drop
没有放置标志,即使它们具有相同的移动语义。³ 这类似于 Kotlin 中空检查的工作方式:如果编译器可以确定一个引用绝对是非空的,它将允许您取消引用它。 Rust 中的有效性分析比这更保守;编译器甚至不会尝试猜测。
关于if-statement - 在 Rust 程序中,当条件为 "if"时,会执行 "false"语句,如何理解?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59222703/