How would you go about creating a stack-allocated vector-like container with some fixed upper limit on the number of elements it can contain? You can see my attempt at this below, but it doesn't compile:
// The following is at crate level
#![feature(unsafe_destructor)]
use std::mem;
use std::ptr;
use std::slice::Iter;
pub struct StackVec<T> {
buf: [T; 10],
len: usize,
}
impl<T> StackVec<T> {
pub fn new() -> StackVec<T> {
StackVec {
buf: unsafe { mem::uninitialized() },
len: 0,
}
}
pub fn iter(&self) -> Iter<T> {
(&self.buf[..self.len]).iter()
}
pub fn push(&mut self, value: T) {
unsafe { ptr::write(self.buf.get_mut(self.len).unwrap(), value); }
self.len += 1;
}
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
unsafe {
self.len -= 1;
Some(ptr::read(self.buf.get(self.len).unwrap()))
}
}
}
}
#[unsafe_destructor]
impl<T> Drop for StackVec<T>
where T: Drop
{
fn drop(&mut self) {
for elem in self.iter() {
unsafe { ptr::read(elem); }
}
unsafe { mem::forget(self.buf); } // ERROR: [1]
}
}
This is the compile-time error I get:
[1] error: cannot move out of type stackvec::StackVec<T>
, which defines the Drop
trait
I've written an implementation, and I'll go over the highlights.
Full code is available at crates.io/arrayvec (API doc)
Use a trait (called
Array
) to abstract over different array sizes. It needs to provide raw pointers so that we can use the array as backing storage.We need to suppress the default drop of the embedded array. You can do this by in theory using
Option<Array>
and usingptr::write
to overwrite it withNone
at the last moment inDrop
.We must however use our own enum, similar to
Option
for one reason: We need to avoid non-nullable pointer optimization that applies to enums that have the same representation asOption
. Then in Drop we do the crucial inhibition of the inner array's default destructor: we forcibly overwrite our enum. Only after destructing all the elements, of course.Deref<Target=[T]>
andDerefMut
and get tons of slice methods for free. This is a great feature of Rust!Flag<A>
is alwaysFlag::Alive(A)
when the value is alive. We should be able to optimize with this in mind. (A FIXME is marked there.)Thank you kmky for asking question! Exploring this answer led to the creation of
arrayvec
linked above, and uncovered some of the points that were very important to have it be a safe rust data structure.My guess is that the compiler doesn't know which elements of the array are "free" and which need a destructor to run when the array is dropped.
Try storing
Option<T>
, which has a.take()
method that will allow you to move an element out of the array.