簡體   English   中英

如何使用返回可變引用的迭代器創建自己的數據結構?

[英]How can I create my own data structure with an iterator that returns mutable references?

我在 Rust 中創建了一個數據結構,我想為它創建迭代器。 不可變迭代器很容易。 我目前有這個,它工作正常:

// This is a mock of the "real" EdgeIndexes class as
// the one in my real program is somewhat complex, but
// of identical type

struct EdgeIndexes;

impl Iterator for EdgeIndexes {
    type Item = usize;
    fn next(&mut self) -> Option<Self::Item> {
        Some(0)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, None)
    }
}

pub struct CGraph<E> {
    nodes: usize,
    edges: Vec<E>,
}

pub struct Edges<'a, E: 'a> {
    index: EdgeIndexes,
    graph: &'a CGraph<E>,
}

impl<'a, E> Iterator for Edges<'a, E> {
    type Item = &'a E;

    fn next(&mut self) -> Option<Self::Item> {
        match self.index.next() {
            None => None,
            Some(x) => Some(&self.graph.edges[x]),
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.index.size_hint()
    }
}

我想創建一個返回可變引用的迭代器。 我試過這樣做,但找不到編譯的方法:

pub struct MutEdges<'a, E: 'a> {
    index: EdgeIndexes,
    graph: &'a mut CGraph<E>,
}

impl<'a, E> Iterator for MutEdges<'a, E> {
    type Item = &'a mut E;

    fn next(&mut self) -> Option<&'a mut E> {
        match self.index.next() {
            None => None,
            Some(x) => self.graph.edges.get_mut(x),
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.index.size_hint()
    }
}

編譯它會導致以下錯誤:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/lib.rs:54:24
   |
54 |             Some(x) => self.graph.edges.get_mut(x),
   |                        ^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 51:5...
  --> src/lib.rs:51:5
   |
51 | /     fn next(&mut self) -> Option<&'a mut E> {
52 | |         match self.index.next() {
53 | |             None => None,
54 | |             Some(x) => self.graph.edges.get_mut(x),
55 | |         }
56 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:54:24
   |
54 |             Some(x) => self.graph.edges.get_mut(x),
   |                        ^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 48:6...
  --> src/lib.rs:48:6
   |
48 | impl<'a, E> Iterator for MutEdges<'a, E> {
   |      ^^
   = note: ...so that the expression is assignable:
           expected std::option::Option<&'a mut E>
              found std::option::Option<&mut E>

我不確定如何解釋這些錯誤以及如何更改我的代碼以允許MutEdges返回可變引用。

使用代碼鏈接到游樂場

您無法編譯它,因為可變引用比不可變引用更具限制性。 說明問題的縮短版本是這樣的:

struct MutIntRef<'a> {
    r: &'a mut i32
}

impl<'a> MutIntRef<'a> {
    fn mut_get(&mut self) -> &'a mut i32 {
        &mut *self.r
    }
}

fn main() {
    let mut i = 42;
    let mut mir = MutIntRef { r: &mut i };
    let p = mir.mut_get();
    let q = mir.mut_get();
    println!("{}, {}", p, q);
}

這會產生相同的錯誤:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
 --> src/main.rs:7:9
  |
7 |         &mut *self.r
  |         ^^^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 6:5...
 --> src/main.rs:6:5
  |
6 | /     fn mut_get(&mut self) -> &'a mut i32 {
7 | |         &mut *self.r
8 | |     }
  | |_____^
note: ...so that reference does not outlive borrowed content
 --> src/main.rs:7:9
  |
7 |         &mut *self.r
  |         ^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 5:6...
 --> src/main.rs:5:6
  |
5 | impl<'a> MutIntRef<'a> {
  |      ^^
note: ...so that reference does not outlive borrowed content
 --> src/main.rs:7:9
  |
7 |         &mut *self.r
  |         ^^^^^^^^^^^^

如果我們看一下 main 函數,我們會得到兩個可變引用,稱為pq ,它們都是i的內存位置的別名。 這是不允許的。 在 Rust 中,我們不能有兩個可變引用作為別名並且都可用。 這種限制的動機是觀察到突變和別名在內存安全方面不能很好地協同工作。 所以,編譯器拒絕代碼是件好事。 如果編譯這樣的東西,很容易出現各種內存損壞錯誤。

Rust 避免這種危險的方法是保持最多一個可變引用可用。 因此,如果您想基於對 Y 的可變引用創建對 X 的可變引用,其中 X 歸 Y 所有,我們最好確保只要對 X 的引用存在,我們就不能再接觸對 Y 的另一個引用. 在 Rust 中,這是通過生命周期和借用來實現的。 在這種情況下,編譯器會考慮借用原始引用,這也會影響結果引用的生命周期參數。 如果我們改變

fn mut_get(&mut self) -> &'a mut i32 {
    &mut *self.r
}

fn mut_get(&mut self) -> &mut i32 { // <-- no 'a anymore
    &mut *self.r // Ok!
}

編譯器不再抱怨這個get_mut函數。 它現在返回一個帶有匹配&self而不是'a的生命周期參數的引用。 這使mut_get成為您“借用” self的函數。 這就是編譯器抱怨不同位置的原因:

error[E0499]: cannot borrow `mir` as mutable more than once at a time
  --> src/main.rs:15:13
   |
14 |     let p = mir.mut_get();
   |             --- first mutable borrow occurs here
15 |     let q = mir.mut_get();
   |             ^^^ second mutable borrow occurs here
16 |     println!("{}, {}", p, q);
   |                        - first borrow later used here

顯然,編譯器確實考慮mir是借用。 這很好。 這意味着現在只有一種方法可以到達i : p的內存位置。

現在您可能想知道:標准庫作者是如何設法編寫可變向量迭代器的? 答案很簡單:他們使用了不安全的代碼。 沒有其他辦法。 Rust 編譯器根本不知道,每當您向可變向量迭代器詢問下一個元素時,您每次都會獲得不同的引用,而不會兩次獲得相同的引用。 當然,我們知道這樣的迭代器不會給你兩次相同的引用,這使得提供你習慣的這種接口是安全的。 我們不需要“凍結”這樣的迭代器。 如果迭代器返回的引用不重疊,則不必借用迭代器來訪問元素是安全的。 在內部,這是使用不安全代碼(將原始指針轉換為引用)來完成的。

您的問題的簡單解決方案可能是依賴MutItems 這已經是一個庫提供的向量可變迭代器。 因此,您可能只使用它而不是您自己的自定義類型,或者您可以將它包裝在您的自定義迭代器類型中。 如果由於某種原因您不能這樣做,您將不得不編寫自己的不安全代碼。 如果你這樣做,請確保

  • 您不會創建多個該別名的可變引用。 如果你這樣做了,這將違反 Rust 規則並調用未定義的行為。
  • 您不要忘記使用PhantomData類型來告訴編譯器您的迭代器是一種類似引用的類型,其中不允許用更長的生命周期替換生命周期,否則可能會創建一個懸空迭代器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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