IntoIter

イテレータに移行しましょう。 Deref の魔法のおかげで、 iteriter_mut は 既に書かれています。しかし、 Vec が提供できて、スライスが提供できない 2 つの 興味深いイテレータがあります。 into_iterdrain です。

IntoIter は Vec を値として消費します。その結果、その要素を値で返します。 これを有効にするために、 IntoIter が Vec のアロケーションを操作する 必要があります。

IntoIter は始端と終端の両方から読み出せるように、両頭である必要があります。 後ろから読み込むのは単に pop を呼び出すよう実装すればよいのですが、 前から読み出すのはもっと難しいです。 remove(0) を呼び出してもよいのですが、 そのコストは馬鹿馬鹿しい位大きいです。その代わりに、バッファを全く変化させずに、 ptr::read を使って両端から値をコピーするようにします。

これをするために、 C のごく普通の配列のイテレーションのイディオムを使います。 2 つのポインタを生成します。 1 つは配列の最初を指し、もう 1 つは配列の最後の 1 つ後ろの要素を指します。終端から要素を 1 つ取得したい場合は、ポインタが 指している値を読み出して、ポインタを 1 だけ動かします。もし 2 つのポインタが 等価な場合、全要素が読み出されたことを意味します。

読み出しとオフセットの操作の順序は nextnext_back とで逆転することに 注意してください。 next_back では、ポインタは次に読みたい要素の直後の 要素をいつも指しています。対して next では、ポインタは次に読みたい 要素をいつも指しています。なぜこうなのか、 1 つを除いて全ての要素が 既に返された例を見てみましょう。

配列は以下のようになっています。

          S  E
[X, X, X, O, X, X, X]

もし E が、次に返したい値を直接指していたら、返す値が既に存在しない場合と 区別がつかなくなっているでしょう。

イテレーションの途中では気にしないのですが、 IntoIter がドロップされたら Vec を 解放するため、 Vec のアロケーションの情報を保持する必要もあります。

ですから以下のような構造体を使っていきます。

struct IntoIter<T> {
    buf: Unique<T>,
    cap: usize,
    start: *const T,
    end: *const T,
}

そしてこれが初期化のコードです。

impl<T> Vec<T> {
    fn into_iter(self) -> IntoIter<T> {
        // Vec がドロップされてしまうため、 Vec を分配出来ません。
        let ptr = self.ptr;
        let cap = self.cap;
        let len = self.len;

        // Vec をドロップするとバッファを解放してしまうので、ドロップしないようにします。
        mem::forget(self);

        unsafe {
            IntoIter {
                buf: ptr,
                cap: cap,
                start: *ptr,
                end: if cap == 0 {
                    // このポインタのオフセットを取ることが出来ません。アロケートされていないからです!
                    *ptr
                } else {
                    ptr.offset(len as isize)
                }
            }
        }
    }
}

前方へのイテレーションのコードです。

impl<T> Iterator for IntoIter<T> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        if self.start == self.end {
            None
        } else {
            unsafe {
                let result = ptr::read(self.start);
                self.start = self.start.offset(1);
                Some(result)
            }
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = (self.end as usize - self.start as usize)
                  / mem::size_of::<T>();
        (len, Some(len))
    }
}

そして後方へのイテレーションのコードです。

impl<T> DoubleEndedIterator for IntoIter<T> {
    fn next_back(&mut self) -> Option<T> {
        if self.start == self.end {
            None
        } else {
            unsafe {
                self.end = self.end.offset(-1);
                Some(ptr::read(self.end))
            }
        }
    }
}

IntoIter はアロケーションの所有権を受け取るので、それを解放するために Drop を 実装する必要があります。しかし、イテレーションの最中に返されなかった要素を ドロップするためにも Drop も実装する必要があります。

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        if self.cap != 0 {
            // 残っている要素を全てドロップします
            for _ in &mut *self {}

            let align = mem::align_of::<T>();
            let elem_size = mem::size_of::<T>();
            let num_bytes = elem_size * self.cap;
            unsafe {
                heap::deallocate(*self.buf as *mut _, num_bytes, align);
            }
        }
    }
}
関連キーワード:  self, IntoIter, 要素, Vec, cap, let, ptr, ドロップ, ポインタ, end