入栈和出栈
好的。我们可以初始化,可以分配内存。让我们实际实现一些功能!先从 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)))
}
}
}