プッシュとポップ
よし、初期化出来ました。アロケート出来ます。それでは実際にいくつか機能を
実装しましょう! まず 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)))
}
}
}