Rust RawVec

我们遇到了一个很有意思的情况:我们把初始化缓存和释放内存的逻辑在Vec和IntoIter里面一模一样地写了两次。现在我们已经实现了功能,而且发现了逻辑的重复,是时候对代码做一些压缩了。

我们要抽象出(ptr, cap),并赋予它们分配、扩容和释放的逻辑:

struct RawVec<T> {
    ptr: Unique<T>,
    cap: usize,
}

impl<T> RawVec<T> {
    fn new() -> Self {
        assert!(mem::size_of::<T>() != 0, "TODO:实现零尺寸类型的支持");
        RawVec { ptr: Unique::empty(), cap: 0 }
    }

    // 与Vec一样
    fn grow(&mut self) {
        unsafe {
            let align = mem::align_of::<T>();
            let elem_size = mem::size_of::<T>();

            let (new_cap, ptr) = if self.cap == 0 {
                let ptr = heap::allocate(elem_size, align);
                (1, ptr)
            } else {
                let new_cap = 2 * self.cap;
                let ptr = heap::reallocate(self.ptr.as_ptr() as *mut _,
                                            self.cap * elem_size,
                                            new_cap * elem_size,
                                            align);
                (new_cap, ptr)
            };

            // 如果分配或再分配失败,我们会得到null
            if ptr.is_null() { oom() }

            self.ptr = Unique::new(ptr as *mut _);
            self.cap = new_cap;
        }
    }
}

impl<T> Drop for RawVec<T> {
    fn drop(&mut self) {
        if self.cap != 0 {
            let align = mem::align_of::<T>();
            let elem_size = mem::size_of::<T>();
            let num_bytes = elem_size * self.cap;
            unsafe {
                heap::deallocate(self.ptr.as_mut() as *mut _, num_bytes, align);
            }
        }
    }
}

然后像下面这样改写Vec:

pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}

impl<T> Vec<T> {
    fn ptr(&self) -> *mut T { self.buf.ptr.as_ptr() }

    fn cap(&self) -> usize { self.buf.cap }

    pub fn new() -> Self {
        Vec { buf: RawVec::new(), len: 0 }
    }

    // push/pop/insert/remove基本没变,只改变了:
    // self.ptr -> self.ptr()
    // self.cap -> self.cap()
    // self.grow -> self.buf.grow()
}

impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        while let Some(_) = self.pop() {}
        // 释放空间由RawVec负责
    }
}

最后我们可以简化IntoIter:

struct IntoIter<T> {
    _buf: RawVec<T>, // 我们并不关心这个,只是需要它们保持分配空间不被销毁
    start: *const T,
    end: *const T,
}

// next和next_back保持不变,因为它们并没有用到buf

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        // 只需要保证所有的元素都被读到了
        // 缓存会在随后自己清理自己
        for _ in &mut *self {}
    }
}

impl<T> Vec<T> {
    pub fn into_iter(self) -> IntoIter<T> {
        unsafe {
            // 需要使用ptr::read非安全地把buf移出,因为它不是Copy,
            // 而且Vec实现了Drop(所以我们不能销毁它)
            let buf = ptr::read(&self.buf);
            let len = self.len;
            mem::forget(self);

            IntoIter {
                start: *buf.ptr,
                end: buf.ptr.offset(len as isize),
                _buf: buf,
            }
        }
    }
}

现在看起来好多了。

下一章:Rust Drain

Drain和IntoIter基本相同,只不过它并不获取Vec的值,而是借用Vec并且不改变它的分配空间。现在我们只是先最“基本”的全范围(full-range)的版本。use std::marker::PhantomD ...