Iteratorから返されたオブジェクトの可変参照

Aug 23 2020

Iterator隣のアイテムも露出できるものを作りたいです。これらのアイテムも変更したくない限り、問題なく簡単です。しかし、同じ構造の可変バリアントを作成するにはどうすればよいですか?

不変:

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

存続期間が原因で機能しない可変バリアント:

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

コンパイラは次のことを指摘しています。

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

回答

1 trentcl Aug 24 2020 at 00:26

残念ながら、EnumerateNearestNeighbors2DMut正しく作成する方法はありません-それは不健全です。呼び出すnextたびに、への前回の呼び出しから返された参照と&mut重複する可能性のある参照を取得&mutnextます。これは、それが機能した場合、エイリアスを作成することによって参照のルールに違反することを意味し&mutます。

これは、が存在するのと同じ理由ですがstd::slice::Windows、存在しませんWindowsMut(ただしChunksMut、チャンクは重複していないため問題ありません)。

いくつかの問題(ここに1つの例)は似たようなエラーメッセージを表示しますが、unsafe参照されている項目が実際には重複していないため、実際には解決可能です(場合によってはで)。これらのソリューションはここでは機能しません。あなたがすることができれば、それ自体に戻り参照することをイテレータを書く(「ストリーミングイテレータ」)、APIは音作ることができます。ただし、Iteratorこれは許可されていません。

ここにあなたのための3つの可能なオプションがあります。確かに他にもあります。

  • (ネイバーを使用した)可変反復をまったく許可しないでください。共有(&)参照を介して反復を公開するだけです。元のグリッドを変更する必要がある場合は、代わりに変更されたコピーを作成し、反復が完了したら元のグリッドと交換します。これは、各出力が複数の入力に依存する画像フィルターやセルオートマトンのようなものを書いている場合、とにかくあなたが望むものであることがよくあります。

  • クロージャを受け入れ、外部反復の代わりにAPIで内部反復を使用します。したがって、このようなものの代わりに:

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

    あなたはこのようなものを書くでしょう:

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

    この場合、arrayのアイテムへの参照forforeach_2d_neighbors_mutメソッド内のループで取得され、エスケープされません。このAPIは、unsafeコードがなくても非常に簡単に記述できます。

  • 内部可変性を使用する(CellRefCellAtomic???、など)を介して変異させる&必要の代わりに参照&mut。何をしているのかによっては、これが正しい方法かもしれません。がスライスまたはベクトルの場合、タイプを変更せずに内部の可変性にこっそり使用CellIIできることに注意してください。しかし、これはほとんどの場合、私の最初の選択ではありません。