Last active
May 31, 2017 20:11
-
-
Save mikeyhew/635afa3155440b1e2e677070bfe6fed2 to your computer and use it in GitHub Desktop.
A Vec-like data structure for trait objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
provides a `Vec`-like data structure for dynamically sized types, storing them all together in a single buffer. | |
# Questions | |
1. I tried to make this a generic struct, `DSTVec<T: ?Sized>`, but that didn't work because I couldn't cast a `&T` | |
to a raw `TraitObject`, so had to fall back to a macro. Is there a way to get around that? Maybe someday there will | |
be an API to split a pointer into its (address, the_rest) components, in which case I think a generic struct would | |
work. Then this implementation would also work for slices, which is pretty cool. | |
2. When I try to use the `declare_dstvec` macro from another crate, the compiler complains that it's using unstable | |
features, and that I need to declare them at the top of that crate, too. Is there a way around that? | |
3. The test doesn't pass. In particular, I think Callbacks::push requires that its argument is 'static. How to fix? | |
4. The closures being pushed to `callbacks` in `main` don't actually capture anything from their environment, so they're | |
basically just function pointers and are zero-sized. I tried tweaking things so that they do capture something, and I | |
realized that it didn't actually work. So that's fun... | |
*/ | |
#![feature(alloc, raw, unsize)] | |
extern crate alloc; | |
pub use alloc::raw_vec::RawVec; | |
pub use std::marker::Unsize; | |
pub use std::raw::TraitObject; | |
#[macro_export] | |
macro_rules! declare_dstvec { | |
($name:ident, $Trait:ty) => { | |
use ::std::{ptr, mem}; | |
use $crate::Unsize; | |
use $crate::RawVec; | |
use $crate::TraitObject; | |
use ::std::vec; | |
pub struct $name { | |
pointers: Vec<(usize, *mut ())>, | |
data: RawVec<u8>, | |
used_bytes: usize, | |
} | |
impl $name { | |
pub fn new() -> $name { | |
let data = RawVec::new(); | |
$name { | |
pointers: Vec::new(), | |
data: data, | |
used_bytes: 0, | |
} | |
} | |
pub fn push<U: Unsize<$Trait>>(&mut self, value: U) { | |
let align = mem::align_of::<U>(); | |
let gap = self.used_bytes + align - (self.used_bytes % align); | |
let size = mem::size_of::<U>(); | |
self.data.reserve(self.used_bytes, gap + size); | |
self.pointers.reserve(1); | |
unsafe { | |
let back = self.data.ptr().offset((gap + size) as isize); | |
ptr::copy_nonoverlapping(&value, back as *mut U, 1); | |
} | |
let offset = self.used_bytes + gap; | |
let vtable = unsafe { | |
let fat_pointer = &value as &$Trait; | |
let trait_object: TraitObject = mem::transmute(fat_pointer); | |
trait_object.vtable | |
}; | |
self.pointers.push((offset, vtable)); | |
self.used_bytes += gap + size; | |
mem::forget(value); | |
} | |
} | |
impl Drop for $name { | |
fn drop(&mut self) { | |
for &(offset, vtable) in self.pointers.iter() { | |
unsafe { | |
let pointer = construct_fat_pointer(self.data.ptr(), offset, vtable); | |
ptr::drop_in_place(pointer); | |
} | |
} | |
} | |
} | |
impl IntoIterator for $name { | |
type Item = *mut $Trait; | |
type IntoIter = IntoIter; | |
fn into_iter(mut self) -> IntoIter { | |
let pointers = mem::replace(&mut self.pointers, Vec::new()); | |
let data = mem::replace(&mut self.data, RawVec::new()); | |
IntoIter { | |
pointers: pointers.into_iter(), | |
data: data, | |
} | |
} | |
} | |
pub struct IntoIter { | |
pointers: vec::IntoIter<(usize, *mut ())>, | |
data: RawVec<u8>, | |
} | |
impl Iterator for IntoIter { | |
type Item = *mut $Trait; | |
fn next(&mut self) -> Option<*mut $Trait> { | |
self.pointers.next().map(|(offset, vtable)| { | |
unsafe { | |
construct_fat_pointer(self.data.ptr(), offset, vtable) | |
} | |
}) | |
} | |
fn size_hint(&self) -> (usize, Option<usize>) { | |
self.pointers.size_hint() | |
} | |
} | |
impl ExactSizeIterator for IntoIter {} | |
impl Drop for IntoIter { | |
fn drop(&mut self) { | |
let offsets_with_vtables = mem::replace(&mut self.pointers, Vec::new().into_iter()); | |
for (offset, vtable) in offsets_with_vtables { | |
unsafe { | |
let pointer = construct_fat_pointer(self.data.ptr(), offset, vtable); | |
ptr::drop_in_place(pointer); | |
} | |
} | |
} | |
} | |
unsafe fn construct_fat_pointer(base: *mut u8, offset: usize, vtable: *mut ()) -> *mut $Trait { | |
mem::transmute(TraitObject { | |
data: base.offset(offset as isize) as *mut (), | |
vtable: vtable, | |
}) | |
} | |
}; | |
} | |
use std::ptr; | |
pub trait FnOnceUnsafe { | |
unsafe fn call_once_unsafe(&mut self); | |
} | |
impl<F: FnOnce()> FnOnceUnsafe for F { | |
unsafe fn call_once_unsafe(&mut self) { | |
ptr::read(self)() | |
} | |
} | |
mod callbacks { | |
use super::FnOnceUnsafe; | |
declare_dstvec!(Callbacks, FnOnceUnsafe); | |
} | |
use self::callbacks::Callbacks; | |
#[test] | |
fn test_closures() { | |
let mut foo = 5; | |
let mut bar = 10; | |
let mut callbacks = Callbacks::new(); | |
callbacks.push(|| { | |
foo = 20; | |
}); | |
callbacks.push(|| { | |
bar = 30; | |
}); | |
for callback in callbacks { | |
unsafe { | |
(*callback).call_once_unsafe(); | |
} | |
} | |
assert_eq!(foo, 20); | |
assert_eq!(bar, 30); | |
} | |
fn main() { | |
let mut callbacks = Callbacks::new(); | |
callbacks.push(|| { | |
println!("Hello"); | |
}); | |
callbacks.push(|| { | |
println!("World!"); | |
}); | |
for callback in callbacks { | |
unsafe { | |
(*callback).call_once_unsafe(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This gist has been moved into a repository