Referência mutável no objeto retornado do Iterator

Aug 23 2020

Eu quero criar um Iteratorcapaz de expor itens vizinhos também. Contanto que eu não queira alterar esses itens, é bom e fácil. Mas como fazer uma variante mutável da mesma estrutura?

Imutável:

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 mutável que não funciona devido a tempos de vida:

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],
            }
        )
    }
}

O compilador aponta que:

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

Respostas

1 trentcl Aug 24 2020 at 00:26

Infelizmente, não há como EnumerateNearestNeighbors2DMutser feito corretamente - é incorreto. Cada vez que você chama, nextobtém &mutreferências que potencialmente se sobrepõem às &mutreferências que foram retornadas da chamada anterior para next. Isso significa que, se funcionasse, estaria violando as regras de referências ao criar aliases &mut.

Este é o mesmo motivo pelo qual existe um std::slice::Windows, mas não WindowsMut(embora ChunksMutseja bom porque os pedaços não se sobrepõem).

Alguns problemas ( aqui está um exemplo ) fornecem mensagens de erro de aparência semelhante, mas são realmente solucionáveis ​​(em alguns casos com unsafe) porque os itens sendo referenciados não se sobrepõem de fato. Essas soluções não funcionam aqui. Se você pudesse escrever um iterador que retornasse referências a si mesmo (um "iterador de streaming"), a API poderia se tornar sólida. No entanto, Iteratornão permite isso.

Aqui estão três opções possíveis para você. Certamente existem outros.

  • Não permite iteração mutável (com vizinhos). Apenas exponha a iteração por meio da &referência shared ( ), e se você precisar alterar a grade original, em vez disso, crie uma cópia modificada e troque-a com a original após terminar a iteração. Freqüentemente, é isso que você deseja de qualquer maneira, se estiver escrevendo algo como um filtro de imagem ou um autômato celular em que cada saída depende de várias entradas.

  • Aceite um encerramento e use iteração interna em sua API em vez de iteração externa. Então, em vez de algo assim:

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

    você escreveria algo assim:

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

    Nesse caso, as referências aos arrayitens de são feitas em um forloop dentro do foreach_2d_neighbors_mutmétodo e não escapam dele. Esta API pode ser escrita com bastante facilidade, mesmo sem unsafecódigo.

  • Use mutabilidade interior ( Cell, RefCell, Atomic???, etc.) para mutar através de uma &referência em vez de necessitar &mut. Dependendo do que você está fazendo, este pode ser o caminho certo a seguir. Esteja ciente de que você pode usar sorrateiramente Cellpara mutabilidade interior sem ter que alterar o tipo I, quando Ié uma fatia ou vetor . No entanto, esta não seria minha primeira escolha na maioria das vezes.