與 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>
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zfTA0M2r-5f-3wGmjmr1rA