與 Rust 編譯器的鬥爭 - 2
讓我們想象一下用 Rust 編寫一個簡單的鏈表。具體來說,讓我們實現 push_front() 方法,它在鏈表的頭部插入一個元素。
代碼如下:
struct Node<T> {
val: T,
next: Option<Box<Node<T>>>,
}
struct LinkedList<T> {
head: Option<Box<Node<T>>>,
}
impl<T> LinkedList<T> {
fn push_front(&mut self, val: T) {
self.head = Some(Box::new(Node{val, next: self.head}))
}
}
我們用給定的值創建一個新節點,用當前 head 值賦值給它的 next 字段,然後設置 self.head 爲新節點的 head。很漂亮,除了 Rust 編譯器會抱怨:
error[E0507]: cannot move out of `self.head` which is behind a mutable reference
--> <source>:12:51
|
12 | self.head = Some(Box::new(Node{val, next: self.head}))
| ^^^^^^^^^ move occurs because `self.head` has type `Option<Box<Node<T>>>`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0507`.
Compiler returned: 1
你也可以在編譯器資源管理器中自己嘗試一下。讓我們深入瞭解一下編譯器爲什麼會報錯,根本原因有兩個方面:
1,在可變引用 & mut self 之後,我們不能移動 self.head 值,就像編譯器說的那樣。這是因爲如果我們移動 self.head 給其他變量,那麼 self.head 進行 drop 之後就成爲無效的,這樣 self 也變成無效的了
2,Rust 編譯器還沒有那麼聰明。在代碼中我們移動 self.head 到新的 node 中,然後我們立即給 self.head 賦一個新的有效值 “self.head = Some(Box::new(Node{…}))”,不幸的是,編譯器不能分析那麼遠。它只能分析一步,self.head 被移出,這是不允許的。
解決的辦法是確保 self.head 總是有效的。也就是說,如果我們要移走它的值,那麼我們需要同時爲它提供一個新值——不是字面上的,而是邏輯上的。這是通過 std::mem:: replace() 或 std::mem::swap() 函數完成的。在本例中,我們需要使用 std::mem::replace()。
pub fn replace<T>(dest: &mut T, src: T) -> T
所以,我們的解決方案如下:
impl<T> LinkedList<T> {
fn push_front(&mut self, val: T) {
// self.head = Some(Box::new(Node{val, next: self.head}));
let head = std::mem::replace(&mut self.head, None);
self.head = Some(Box::new(Node{val, next: head}))
}
}
我們替換 self.head 爲一個虛擬值,本例爲 None,並將其先前的值保存在一個局部變量 head 中。然後我們創建一個指向 head 的新節點,並設置 self.head 指向新節點。這是一個通用的解決方案,同時也有一個特定的 Option 類型的方法:Option::take() 也是按上面的方式這樣做的。因爲我們的 head 變量是 Option<…> 類型,所以我們也可以像下面這樣簡化代碼:
impl<T> LinkedList<T> {
fn push_front(&mut self, val: T) {
// self.head = Some(Box::new(Node{val, next: self.head}));
self.head = Some(Box::new(Node{val, next: self.head.take()}));
}
}
現在留一個作業,嘗試實現 pop_front() 方法,它應該具有如下所示的簽名。
fn pop_front(&mut self) -> Option<T>
-
如果頭節點爲空,則返回 None
-
否則更新 head 以指向第二個節點並返回第一個值
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/zfTA0M2r-5f-3wGmjmr1rA