プッシュとポップ

よし、初期化出来ました。アロケート出来ます。それでは実際にいくつか機能を 実装しましょう! まず push 。必要なことは、 grow をするべきか確認し、 状況によらず、次のインデックスの場所に書き込み、そして長さをインクリメント します。

書き込みの際、書き込みたいメモリの値を評価しないよう、注意深く行なう必要が あるます。最悪、そのメモリはアロケータが全く初期化していません。良くても、 ポップした古い値のビットが残っています。いずれにせよ、そのメモリをインデックス指定し、 単に参照外しをするわけにはいきません。なぜならそれによって、メモリ上の値を、 T の 正しいインスタンスとして評価してしまうからです。もっと悪いことに、 foo[idx] = x によって、 foo[idx] の古い値に対して drop を呼ぼうとしてしまいます!

正しい方法は、 ptr::write を使う方法です。これは、ターゲットのアドレスを、 与えた値のビットでそのまま上書きします。何の評価も起こりません。

push においては、もし古い (push が呼ばれる前の) len が 0 であるなら、 0 番目の インデックスに書き込むようにしたいです。ですから古い len の値によるオフセットを使うべきです。

pub fn push(&mut self, elem: T) {
    if self.len == self.cap { self.grow(); }

    unsafe {
        ptr::write(self.ptr.offset(self.len as isize), elem);
    }

    // 絶対成功します。 OOM はこの前に起こるからです。
    self.len += 1;
}

簡単です! では pop はどうでしょうか? この場合、アクセスしたいインデックスにある 値は初期化されていますが、 Rust はメモリ上の値をムーブするために、その場所への 参照外しをする事を許可しません。なぜならこれによって、メモリを未初期化のままにするからです! これに関しては、 ptr::read を必要とします。これは、単にターゲットのアドレスから ビットをコピーし、それを型 T の値として解釈します。これによって、実際には そのアドレスのメモリにある値は完全に T のインスタンスであるけれども、 値を論理的には未初期化の状態のままにします。

pop に関しては、もし古い len の値が 1 の場合、 0 番目のインデックスにある値を 読み出したいです。ですから新しい len によるオフセットを使うべきです。

pub fn pop(&mut self) -> Option<T> {
    if self.len == 0 {
        None
    } else {
        self.len -= 1;
        unsafe {
            Some(ptr::read(self.ptr.offset(self.len as isize)))
        }
    }
}