Конфликт времен жизни


#1

С реализацией итератора имутабельных значений проблем нет:

impl<'a, V: 'a> Iterator for UnitIter<'a, Joint, V> {
    type Item = (Joint, &'a V);
    fn next(&mut self) -> Option<Self::Item> {
        self.index += 1;
        match self.index {
            1 => Some((Joint::J0, &self.unit.0[0])),
            2 => Some((Joint::J1, &self.unit.0[1])),
            3 => Some((Joint::J2, &self.unit.0[2])),
            4 => Some((Joint::J3, &self.unit.0[3])),
            5 => Some((Joint::J4, &self.unit.0[4])),
            6 => Some((Joint::J5, &self.unit.0[5])),
            7 => Some((Joint::J6, &self.unit.0[6])),
            8 => Some((Joint::J7, &self.unit.0[7])),
            _ => None,
        }
    }
}

а при реализации итератора мутабельных значение возникает конфликт времен жизни:

impl<'a, V: 'a> Iterator for UnitIterMut<'a, Joint, V> {
    type Item = (Joint, &'a mut V);
    fn next(&mut self) -> Option<Self::Item> {
        self.index += 1;
        match self.index {
            1 => Some((Joint::J0, &mut self.unit.0[0])),
            2 => Some((Joint::J1, &mut self.unit.0[1])),
            3 => Some((Joint::J2, &mut self.unit.0[2])),
            4 => Some((Joint::J3, &mut self.unit.0[3])),
            5 => Some((Joint::J4, &mut self.unit.0[4])),
            6 => Some((Joint::J5, &mut self.unit.0[5])),
            7 => Some((Joint::J6, &mut self.unit.0[6])),
            8 => Some((Joint::J7, &mut self.unit.0[7])),
            _ => None,
        }
    }
}

Почему так?
Playground


#2

Потому что компилятор не может гарантировать, что ты не ошибешься в индексах и не напишешь кашу вроде:

1 => Some((Joint::J0, &mut self.unit.0[0])),
2 => Some((Joint::J1, &mut self.unit.0[1])),
3 => Some((Joint::J2, &mut self.unit.0[1])), // copypaste typo
4 => Some((Joint::J3, &mut self.unit.0[3])),

Если ты ошибешься, то будет две мутабельные ссылки на один регион, что является UB.

Твою задачу можно решить через unsafe, посмотри в теме https://users.rust-lang.org/t/implementing-an-iterator-of-mutable-references/8671 , по той же самой причине не может быть iter_mut для оконного итерирования: https://users.rust-lang.org/t/iterator-over-mutable-windows-of-slice/17110/2


#3

Не думаю что тут дело в индексах.
Пока не реализуют GATs такое сделать невозможно (и скорее всего GATs тоже не помогут см. ниже). Я раньше кидал ссылку на статью со способами решения этой проблемы ( Solving the Generalized Streaming Iterator Problem without GATs ), но на практике они все малополезны.
Через unsafe тоже очень не советую это делать. Тут все завязано на особенности мутабельных ссылок - эксклюзивность и инвариантность относительно типа.

Если отбросить лишнее (типаж Iterator) и явно указать вж, функцию next() можно записать так:

fn next<'b, 'a, V>(self: &'b mut UnitIterMut<'a, Joint, V>) -> Option<(Joint, &'a mut V)> {
    Some((Joint::J0, &mut self.unit.0[0]))
}

Если пойти еще дальше все можно свести к такой функции:

fn next<'b, 'a, V>(slf: &'b mut &'a mut V) -> &'a mut V {
    &mut **slf
}

Такая функция в принципе не реализуема, кроме случая когда 'b:'a (вж 'b не меньше чем 'a, в данном случе равно). Я об этом раньше писал в теме: Странное поведение вложенных мутабельных ссылок.
А типаж Iterator без GATs не дает возможности описать связь между вж ассоциированного типа и Self.


#4

Посмотрел как сделан IterMut из стандартной библиотеки и сделал так-же через unsafe:

pub struct IterMut<'a, P, V> {
    unit: *mut Unit<P, V>,
    index: Range<usize>,
    marker: PhantomData<&'a mut Unit<P, V>>,
}

impl<'a, V: 'a> Iterator for IterMut<'a, Joint, V> {
    type Item = (Joint, &'a mut V);
    fn next(&mut self) -> Option<Self::Item> {
        self.index.next().map(|n| {
            (Joint::try_from(n).unwrap(), unsafe {
                &mut (*self.unit).0[n]
            })
        })
    }
}


#5

Похоже я слишком раскатал губу насчет GATs. Они предназначены для случаев когда структура владеет данными, а не ссылается на них.

Даже если GATs позволят связывать вж, как я писал раньше, то такой итератор будет совершенно бесполезен. После первого вызова метода next() произойдет “вечное” заимствование структуры итератора и второй раз его вызвать будет нельзя, никакие ограничения области видимости не помогут. Все из-за одной интересной особенности при вычислении длительности заимствования данных компилятором: Почему переменная data заимствуется после вызова функции?

Так что кроме unsafe вариантов нет.


#6

Я не уверен, что ты все сделал правильно.

Пожалуйста, не делайте так, вообще старайтесь не использовать unsafe, это крайне опасно.


#7

А где я ошибся?

Не думаю что использование сырых указателей,
опаснее чем использование указателей в C/C++,
которыми я тоже пользуюсь.
И почему ребята которые пишут стандартную библиотеку не должны боятся, а я должен?
Вообщем странный совет.


#8

Они боятся. И перепроверяют. И потом находят баги: https://github.com/rust-lang/miri#bugs-found-by-miri

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

Пожалуйста, не делайте так, вообще старайтесь не использовать unsafe, это крайне опасно.


#9

Как-то это чрезмерно. Некоторые вещи правильно делать только через unsafe.