Подтвердить что ты не робот

Как я могу создать свою собственную структуру данных с помощью итератора, который возвращает изменчивые ссылки?

Я создал структуру данных в 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 мог возвращать изменяемые ссылки.

Ссылка на площадку с кодом.

4b9b3361

Ответ 1

Вы не можете скомпилировать это, потому что изменяемые ссылки более строгие, чем неизменные. Сокращенная версия, иллюстрирующая проблему:

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
  |         ^^^^^^^^^^^^

Если мы посмотрим на основную функцию, мы получим две изменяемые ссылки p именами p и q которые оба псевдоним ячейки памяти i. Это не разрешено В Rust у нас не может быть двух изменяемых ссылок, которые являются псевдонимами и могут использоваться. Мотивация для этого ограничения - наблюдение, что мутация и алиасинг не очень хорошо сочетаются с безопасностью памяти. Итак, хорошо, что компилятор отклонил код. Если что-то подобное скомпилировано, было бы легко получить всевозможные ошибки повреждения памяти.

Способ, которым Rust избегает такого рода опасностей, заключается в том, что можно использовать не более одной изменяемой ссылки. Итак, если вы хотите создать изменяемую ссылку на X на основе изменяемой ссылки на Y, где 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 решением вашей проблемы может быть использование MutItems. Это уже предоставляемый библиотекой изменяемый итератор для вектора. Таким образом, вы могли бы избежать использования только этого вместо вашего собственного пользовательского типа, или вы могли бы обернуть его внутри вашего пользовательского типа итератора. Если по какой-то причине вы не можете этого сделать, вам придется написать собственный небезопасный код. И если вы сделаете это, убедитесь, что

  • Вы не создаете несколько изменяемых ссылок, которые псевдоним. Если вы это сделаете, это нарушит правила Rust и вызовет неопределенное поведение.
  • Вы не забудьте использовать тип PhantomData чтобы сообщить компилятору, что ваш итератор является ссылочным типом, где замена времени жизни более длинным недопустима и в противном случае может создать висячий итератор.