[英]Why is the “move” keyword necessary when it comes to threads; why would I ever not want that behavior?
例如(取自Rust 文檔):
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
這不是關於move
做什么的問題,而是關於為什么需要指定.
如果您希望閉包擁有外部值的所有權,是否有理由不使用move
關鍵字? 如果在這些情況下總是需要move
,是否有任何理由不能僅僅暗示/省略move
的存在? 例如:
let v = vec![1, 2, 3];
let handle = thread::spawn(/* move is implied here */ || {
// Compiler recognizes that `v` exists outside of this closure's
// scope and does black magic to make sure the closure takes
// ownership of `v`.
println!("Here's a vector: {:?}", v);
});
上面的示例給出了以下編譯錯誤:
closure may outlive the current function, but it borrows `v`, which is owned by the current function
當錯誤通過添加move
神奇地消失時,我不禁對自己想知道:為什么我不想要這種行為?
我並不是說所需的語法有任何問題。 我只是想從比我更了解move
的人那里獲得更深入的了解。 :)
這完全是關於生命周期注釋,以及很久以前 Rust 做出的設計決定。
看,您的thread::spawn
示例無法編譯的原因是因為它需要一個'static
閉包”。 由於新線程的運行時間比生成它的代碼長,因此我們必須確保任何捕獲的數據在調用者返回后仍然有效。 正如您所指出的,解決方案是使用move
傳遞數據的所有權。
但是'static
約束”是生命周期注釋,Rust 的基本原則是生命周期注釋永遠不會影響運行時行為。 換句話說,生命周期注解只是為了讓編譯器相信代碼是正確的; 他們不能改變代碼的作用。
如果 Rust 根據被調用者是否期望'static
推斷出move
關鍵字,則在刪除捕獲的數據時更改thread::spawn
中的生命周期可能會發生變化。 這意味着生命周期注釋正在影響運行時行為,這違反了這一基本原則。 我們不能打破這個規則,所以move
關鍵字仍然存在。
讓我們可以自由地改變生命周期推斷的工作方式,從而允許改進非詞匯生命周期 (NLL) 。
因此,像mrustc這樣的替代 Rust 實現可以通過忽略生命周期來節省工作量。
大部分編譯器都假設生命周期以這種方式工作,因此要不然就需要付出巨大的努力並獲得可疑的收益。 (請參閱Aaron Turon 的這篇文章;它是關於專業化的,而不是閉包,但它的觀點同樣適用。)
這里實際上有一些事情在起作用。 為了幫助回答您的問題,我們必須首先了解move
存在的原因。
Rust 有 3 種類型的閉合:
創建閉包時,Rust 根據閉包使用環境值的方式推斷要使用的特征。 閉包捕獲其環境的方式取決於其類型。 FnOnce
按值捕獲(如果類型是可Copy
的,則可能是移動或副本), FnMut
可變借用,而Fn
不可變借用。 但是,如果在聲明閉包時使用move
關鍵字,它將始終“按值捕獲”,或者在捕獲之前取得環境的所有權。 因此, move
關鍵字與FnOnce
無關,但它改變了Fn
和FnMut
捕獲數據的方式。
以您的示例為例, Rust 將閉包的類型推斷為Fn
,因為println!
只需要引用它正在打印的值(您鏈接的 Rust 書頁在解釋錯誤時沒有move
時談到了這一點)。 因此,閉包嘗試借用v
,並且適用標准生命周期規則。 由於thread::spawn
要求傳遞給它的閉包具有'static
生命周期,因此捕獲的環境還必須具有'static
生命周期, v
不會超過該生命周期,從而導致錯誤。 因此,您必須明確指定您希望閉包擁有v
的所有權。
這可以通過將閉包更改為編譯器推斷為FnOnce
的東西來進一步舉例說明 -- || v
|| v
,作為一個簡單的例子。 由於編譯器推斷閉包是一個FnOnce
,它默認捕獲v
的值,並且行let handle = thread::spawn(|| v);
無需move
即可編譯。
現有的答案有很好的信息,這讓我有了一個更容易思考的理解,希望其他 Rust 新手更容易獲得。
考慮這個簡單的 Rust 程序:
fn print_vec (v: &Vec<u32>) {
println!("Here's a vector: {:?}", v);
}
fn main() {
let mut v: Vec<u32> = vec![1, 2, 3];
print_vec(&v); // `print_vec()` borrows `v`
v.push(4);
}
現在,問為什么不能隱含move
關鍵字就像問為什么不能隱含print_vec(&v)
中的“&”。
Rust 的核心特征是所有權。 你不能只告訴編譯器,“嘿,這是我寫的一堆代碼,現在請完美識別我打算引用、借用、復制、移動等的任何地方。Kthnxsbye!” &
和move
等符號和關鍵字是該語言必不可少的組成部分。
事后看來,這似乎很明顯,讓我的問題看起來有點傻!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.