Riferimento modificabile nell'oggetto restituito da Iterator

Aug 23 2020

Voglio creare un in Iteratorgrado di esporre anche gli oggetti vicini. Finché non voglio cambiare anche questi elementi, va bene e facile. Ma come realizzare una variante mutevole della stessa struttura?

Immutabile:

struct NearestNeighbours2D<'a, T> {
    mid: &'a T,
    left: &'a T,
    right: &'a T,
    top: &'a T,
    bot: &'a T,
}

struct EnumerateNearestNeighbours2D<'a, I>
where I: std::ops::Index<usize> {
    x: usize,
    y: usize,
    width: usize,
    height: usize,
    inner: &'a I
}

impl<'a, I: std::ops::Index<usize>> Iterator for EnumerateNearestNeighbours2D<'a, I>
where <I as std::ops::Index<usize>>::Output: std::marker::Sized {
    
    type Item = NearestNeighbours2D<'a, I::Output>;
    fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {

        let (top, left, mid, right, bot) = (
            (self.y - 1) * self.width + self.x,
            self.y * self.width + self.x - 1,
            self.y * self.width + self.x,
            self.y * self.width + self.x + 1,
            (self.y + 1) * self.width + self.x,
        );

        Some(
            NearestNeighbours2D {
                mid: &self.inner[mid],
                left: &self.inner[left],
                right: &self.inner[right],
                top: &self.inner[top],
                bot: &self.inner[bot],
            }
        )
    }
}

Variante mutevole che non funziona a causa della durata:

struct NearestNeighbours2DMut<'a, T> {
    mid: &'a mut T,
    left: &'a mut T,
    right: &'a mut T,
    top: &'a mut T,
    bot: &'a mut T,
}

struct EnumerateNearestNeighbours2DMut<'a, I>
where I: std::ops::IndexMut<usize> {
    x: usize,
    y: usize,
    width: usize,
    height: usize,
    inner: &'a mut I
}

impl<'a, I: std::ops::IndexMut<usize>> Iterator for EnumerateNearestNeighbours2DMut<'a, I>
where <I as std::ops::Index<usize>>::Output: std::marker::Sized {
    
    type Item = NearestNeighbours2DMut<'a, I::Output>;
    fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {

        let (top, left, mid, right, bot) = (
            (self.y - 1) * self.width + self.x,
            self.y * self.width + self.x - 1,
            self.y * self.width + self.x,
            self.y * self.width + self.x + 1,
            (self.y + 1) * self.width + self.x,
        );

        Some(
            NearestNeighbours2DMut {
                mid: &mut self.inner[mid],
                left: &mut self.inner[left],
                right: &mut self.inner[right],
                top: &mut self.inner[top],
                bot: &mut self.inner[bot],
            }
        )
    }
}

Il compilatore sottolinea che:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
   --> src\lib.rs:99:27
    |
99  |                 mid: &mut self.inner[mid],
    |                           ^^^^^^^^^^^^^^^
    |

Risposte

1 trentcl Aug 24 2020 at 00:26

Sfortunatamente, non è EnumerateNearestNeighbors2DMutpossibile farlo correttamente: non è corretto. Ogni volta che si chiama nextsi ottengono &mutriferimenti che potenzialmente si sovrappongono a &mutriferimenti restituiti dalla chiamata precedente a next. Ciò significa che, se funzionasse, violerebbe le regole dei riferimenti creando &muts alias .

Questo è lo stesso motivo per cui è presente un std::slice::Windows, ma no WindowsMut(anche se ChunksMutva bene perché i blocchi non si sovrappongono).

Alcuni problemi ( ecco un esempio ) danno messaggi di errore simili, ma sono effettivamente risolvibili (in alcuni casi con unsafe) perché gli elementi a cui si fa riferimento non sono in realtà sovrapposti. Quelle soluzioni non funzionano qui. Se si potesse scrivere un iteratore che restituisce riferimenti a se stesso (un "iteratore di streaming"), l'API potrebbe essere resa sonora. Tuttavia, Iteratornon lo consente.

Ecco tre possibili opzioni per te. Ce ne sono sicuramente altri.

  • Non consentire affatto l'iterazione mutevole (con i vicini). Esponi semplicemente l'iterazione tramite il &riferimento shared ( ) e, se devi cambiare la griglia originale, crea invece una copia modificata e scambiala con l'originale dopo aver finito di iterare. Questo è spesso quello che vuoi comunque, se stai scrivendo qualcosa come un filtro di immagine o un automa cellulare in cui ogni output dipende da più input.

  • Accetta una chiusura e utilizza l'iterazione interna nella tua API anziché l'iterazione esterna. Quindi, invece di qualcosa di simile:

    for neighbors in array.enumerate_2d_neighbors_mut() {
        println!("{}", neighbors.top);
    }
    

    scriveresti qualcosa del genere:

    array.foreach_2d_neighbors_mut(|neighbors| {
        println!("{}", neighbors.top);
    });
    

    In questo caso i riferimenti agli arrayelementi di vengono presi in un forciclo all'interno del foreach_2d_neighbors_mutmetodo e non ne escono. Questa API può essere scritta abbastanza facilmente, anche senza unsafecodice.

  • Utilizzare mutabilità interno ( Cell, RefCell, Atomic???, ecc) per mutare attraverso un &riferimento invece di aver bisogno &mut. A seconda di cosa stai facendo, questa potrebbe essere la strada giusta da percorrere. Tieni presente che puoi usarlo di nascosto Cellper la mutabilità interna senza dover cambiare il tipo I, quando Iè una fetta o un vettore . Tuttavia questa non sarebbe la mia prima scelta la maggior parte delle volte.