前回 に引き続き、
The Rustnomicon の Implementing Vec
をやってみる。
コード全体は GitHub上のリポジトリ にある。
rustcのバージョンは以下のとおり。
$ rustc --version
rustc 1.25.0-nightly (27a046e93 2018-02-18)
Push and Pop
メモリ確保ができるようになったので、push, popを実装する。
便利メソッドとして OwnedPtr
から *mut T
を取り出す関数を作っておく。
impl<T: ?Sized> OwnedPtr<T> {
pub(crate) fn as_ptr(&self) -> *mut T {
self.ptr.as_ptr()
}
}
まずは Vec::push()
だが、素直に実装すればよい。
確保したメモリ領域が足りなくなったら伸ばし、ptr::write()
で要素を書き込む。
書き込むアドレスは、OwnedPtr
から取り出した *mut T
に self.len
だけオフセットを加えたものとする。
ptr::write()
時にpanicした場合を考慮して、self.len
のインクリメントは最後におこなう。
impl<T> Vec<T> {
pub fn push(&mut self, elem: T) {
if self.len == self.cap {
self.grow();
}
unsafe {
let ptr_last = self.ptr.as_ptr().offset(self.len as isize);
ptr::write(ptr_last, elem);
}
self.len += 1;
}
}
Vec::pop()
も同様に、ptr::read()
を使い実装する。
読み込むアドレスは、OwnedPtr
から取り出した *mut T
に self.len
だけオフセットを加えたものとする。
impl<T> Vec<T> {
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1;
unsafe {
let ptr_last = self.ptr.as_ptr().offset(self.len as isize);
Some(ptr::read(ptr_last))
}
}
}
}
簡単なテストを書き、動作確認しておく。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_pop() {
let mut v = Vec::new();
const ELEM_NUM: usize = 32;
let elems = 0..ELEM_NUM;
for (i, e) in elems.clone().enumerate() {
v.push(e);
assert_eq!(v.len(), i + 1);
}
for (i, e) in elems.rev().enumerate() {
let p = v.pop();
assert!(p.is_some() && p.unwrap() == e);
assert_eq!(v.len(), ELEM_NUM - 1 - i);
}
}
}