簡體   English   中英

如何在沒有克隆的情況下更改對擁有值的引用?

[英]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_instinto_iter調用是不必要的,因為ListHolder已經實現了Iterator所以它不需要變成一個。 您可能還想知道numval以及為什么我們仍然必須為此創建一個臨時變量。

原因是這個值仍然只是一個參考,我們沒有擁有對所有者 ( 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM