[英]How can I change a reference to an owned value without clone?
我正在練習閱讀本書時學到的 Rust 概念。 我已經能夠通過復制Box
並將list
分配給復制的框來迭代我的List
枚舉,但直觀地說,我覺得必須有一種方法來“讓它指向行中的下一個指針”。
如果我嘗試在沒有bx.clone()
的情況下執行此操作,例如: self.list = **bx
,我會得到“無法移出位於可變引用后面的**bx
”。 這意味着我需要擁有它,但我無法獲得擁有的bx
,因為當我在if let
中取消引用它時,我需要將它作為參考移動。
是否可以或建議在不復制參考的情況下移動參考?
#[derive(Clone)]
enum List {
Cons(u32, Box<List>),
Nil,
}
struct ListHolder {
list: List,
}
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = &mut self.list {
let val = *num;
self.list = *bx.clone(); // This is the key line
Some(val)
} else {
None
}
}
}
use List::*;
fn main() {
let list_inst = ListHolder {
list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))),
};
for i in list_inst.into_iter() {
println!("{}", i); // Prints 1, 2, 3 as expected
}
}
我認為你的心理 model 的關鍵問題是你認為Box<T>
只是一個指針。 Rust 引用(以及大多數智能指針,如Box<T>
)不僅是指針,而且是有效指針。 也就是說,沒有 null 引用,並且引用必須始終指向有效數據。
當我們嘗試做self.list = **bx;
,我們將數據從bx
移動到self.list
。 但是, bx
不擁有其數據。 當可變借用bx
結束時,實際所有者將持有無效數據。
那么我們該怎么辦? 最簡單的方法是有時被稱為瓊斯的技巧,我們將bx
中的數據切換為一些虛擬值。 現在bx
中數據的實際所有者將不會持有無效數據。 那么我們該怎么做呢? 這是 function std::mem::replace
的權限,它接受一個可變引用和一個值,並用該值替換可變引用后面的數據,返回之前可變引用后面的內容(包括所有權。)。 這正是我們想要在這里使用self.list = std::mem::replace(&mut **bx, List::Nil)
做的事情。 同樣, List::Nil
只是一些虛擬數據; 任何List
都將完全一樣。
enum List {
Cons(u32, Box<List>),
Nil,
}
struct ListHolder {
list: List,
}
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = &mut self.list {
let val = *num;
self.list = std::mem::replace(&mut **bx, List::Nil); // This is the key line
Some(val)
} else {
None
}
}
}
use List::*;
fn main() {
let list_inst = ListHolder {
list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))),
};
for i in list_inst.into_iter() {
println!("{}", i); // Prints 1, 2, 3 as expected
}
}
為了稍微更慣用一點,我們可以簡單地使用bx.as_mut()
來代替&mut **bx
從框中獲取可變引用。 此外,對list_inst
的into_iter
調用是不必要的,因為ListHolder
已經實現了Iterator
所以它不需要變成一個。 您可能還想知道num
和val
以及為什么我們仍然必須為此創建一個臨時變量。
原因是這個值仍然只是一個參考,我們沒有擁有對所有者 ( self.list
) 的訪問權限。 這意味着我們必須復制它才能返回。 u32
實現了Copy
所以這不是一個真正的問題,但是如果你試圖使鏈表在其元素的類型上是通用的,它根本就行不通。 let val = *num;
是我們以前做不到的那種“搬出借來的內容”。
解決方案是使用std::mem::replace
不僅獲得bx
背后數據的所有權,而且獲得整個列表的所有權。 因此,如果我們在解構之前使用std::mem::replace(&mut self.list, List::Nil)
, self.list
將被替換為虛擬值,我們將擁有實際列表的所有權,包括值和列表的尾部。 這也意味着我們可以只擁有self.list = *bx
,我相信你最初想要的。
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = std::mem::replace(&mut self.list, List::Nil) {
self.list = *bx;
Some(num)
} else {
None
}
}
}
結果是現在您可以毫不費力地使列表通用。
如果你想了解更多關於 Rust 的所有權 model 如何影響鏈表的實現,你可以做的最好的系列是學習 Rust With Entirely Too Many Linked Lists 。 該系列詳細介紹了此處的所有內容以及許多變體。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.