入栈和出栈

好的。我们可以初始化,可以分配内存。让我们实际实现一些功能!先从 push 开始。它需要做的就是检查是否已满以进行扩容,无条件地写入下一个索引,然后增加我们的长度。

为了进行写入,我们必须小心,不要评估我们要写入的内存。最坏的情况是,它是来自分配器的真正未初始化的内存。最好的情况是,它是我们弹出的旧值的一些位。无论哪种情况,我们都不能直接索引到内存并解引用它,因为这将把内存评估为 T 的有效实例。更糟糕的是,foo[idx] = x 会尝试在 foo[idx] 的旧值上调用 drop

正确的做法是使用 ptr::write,它只是盲目地用我们提供的值的位覆盖目标地址。不涉及评估。

对于 push,如果旧的长度(在调用 push 之前)为 0,那么我们要写入第 0 个索引。所以我们应该偏移旧的长度。

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

    unsafe {
        ptr::write(self.ptr.as_ptr().add(self.len), elem);
    }

    // Can't fail, we'll OOM first.
    self.len += 1;
}

简单!那 pop 呢?虽然这次我们要访问的索引是初始化的,但 Rust 不会让我们解引用内存位置来移出值,因为那样会使内存未初始化!为此,我们需要 ptr::read,它只是从目标地址复制出位,并将其解释为 T 类型的值。这将使该地址的内存在逻辑上处于未初始化状态,即使实际上那里有一个完全有效的 T 实例。

对于 pop,例如,如果旧的长度为 1,我们要从第 0 个索引读取。所以我们应该偏移新的长度。

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