2014-09-08 3 views
18

Я создал структуру данных в 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<&'a mut E> for MutEdges<'a, E> { 
    fn next(&mut self) -> Option<&'a mut E> { 
     match (self.index.next()) { 
      None => None, 
      Some(x) => Some(self.graph.edges.get_mut(x)) 
     } 
    } 

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

Компиляция это приводит к следующей ошибке:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements 
    --> src/main.rs:53:24 
    | 
53 |    Some(x) => self.graph.edges.get_mut(x), 
    |      ^^^^^^^^^^^^^^^^ 
    | 
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 50:45... 
    --> src/main.rs:50:46 
    | 
50 |  fn next(&mut self) -> Option<Self::Item> { 
    | ______________________________________________^ 
51 | |   match self.index.next() { 
52 | |    None => None, 
53 | |    Some(x) => self.graph.edges.get_mut(x), 
54 | |   } 
55 | |  } 
    | |_____^ 
note: ...so that reference does not outlive borrowed content 
    --> src/main.rs:53:24 
    | 
53 |    Some(x) => self.graph.edges.get_mut(x), 
    |      ^^^^^^^^^^^^^^^^ 
note: but, the lifetime must be valid for the lifetime 'a as defined on the body at 50:45... 
    --> src/main.rs:50:46 
    | 
50 |  fn next(&mut self) -> Option<Self::Item> { 
    | ______________________________________________^ 
51 | |   match self.index.next() { 
52 | |    None => None, 
53 | |    Some(x) => self.graph.edges.get_mut(x), 
54 | |   } 
55 | |  } 
    | |_____^ 
note: ...so that types are compatible (expected std::iter::Iterator, found std::iter::Iterator) 
    --> src/main.rs:50:46 
    | 
50 |  fn next(&mut self) -> Option<Self::Item> { 
    | ______________________________________________^ 
51 | |   match self.index.next() { 
52 | |    None => None, 
53 | |    Some(x) => self.graph.edges.get_mut(x), 
54 | |   } 
55 | |  } 
    | |_____^ 

Я не уверен, как интерпретировать эти ошибки и как изменить мой код, чтобы позволить MutEdges возвращать изменяемые ссылки.

Ссылка на playground with code.

+0

Я не уверен, но это может быть та же проблема, что и http://stackoverflow.com/questions/25702909/can-i-write-an-iterator-that-yields-a-reference-into -это – Levans

+0

Не совсем, я думаю. Мой итератор не владеет объектами, к которым он возвращает изменчивые ссылки, которые он делает. Я думаю, что это возможно, учитывая, что стандартная библиотека Rust [уже имеет итераторы изменчивых ссылок] (http://doc.rust-lang.org/std/slice/struct.MutItems.html) –

+1

В их реализации используется устаревшая функция ' mut_shift_ref() ', возможно, вы можете найти то, что вам нужно: http://doc.rust-lang.org/std/slice/trait.MutableSlice.html#tymethod.mut_shift_ref – Levans

ответ

21

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

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(); 
} 

Которая производит ту же ошибку:

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 body at 6:41... 
--> src/main.rs:6:42 
    | 
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 body at 6:41... 
--> src/main.rs:6:42 
    | 
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 
    |   ^^^^^^^^^^^^ 

Если мы посмотрим на основных функциях, мы получаем две изменяемых ссылки называемых p и q что оба имеют расположение памяти i. Это запрещено. В 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 функции. Теперь он возвращает ссылку с параметром lifetime, которая больше соответствует &self, а не 'a. Это делает mut_get функцией, с которой вы «занимаете» self. И вот почему компилятор жалуется на другое место:

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

Видимо, компилятор действительно сделал рассмотреть mir заимствованным. Это хорошо. Это означает, что теперь есть только один способ достижения местоположения памяти i: p.

Теперь вы можете задаться вопросом: как авторам стандартной библиотеки удалось написать изменяемый векторный итератор? Ответ прост: они использовали небезопасный код. Другого пути нет. Компилятор Rust просто не знает, что всякий раз, когда вы запрашиваете измененный векторный итератор для следующего элемента, вы получаете разную ссылку каждый раз и никогда не ссылаетесь на эту ссылку дважды. Конечно, мы знаем, что такой итератор не даст вам одну и ту же ссылку дважды. И это позволяет безопасно предлагать такой интерфейс, к которому вы привыкли. Нам не нужно «замораживать» такой итератор. Если ссылки, которые возвращает итератор, не перекрываются, безопасно не вынуждены брать итератор для доступа к элементу. Внутренне это делается с использованием небезопасного кода (превращая исходные указатели в ссылки).

Решение для вашей проблемы может положиться на MutItems. Это уже предоставленный библиотекой изменяемый итератор над вектором. Таким образом, вы можете уйти, просто используя это вместо своего собственного типа, или вы можете обернуть его внутри своего типа итератора. Если вы не можете это сделать по какой-то причине, вам придется написать свой собственный небезопасный код. И если вы это сделаете, убедитесь, что

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

Благодарим вас за подробный ответ. Я думаю, что я мог бы полагаться на MutItems вместо того, чтобы писать свой собственный небезопасный код. –