Skip to content

Instantly share code, notes, and snippets.

@diwic
Created July 24, 2015 21:55
Show Gist options
  • Save diwic/4b86a6ec7bd4ccde94eb to your computer and use it in GitHub Desktop.
Save diwic/4b86a6ec7bd4ccde94eb to your computer and use it in GitHub Desktop.
#![feature(core, alloc, unique)]
use std::{ptr, mem, intrinsics, ops};
use std::rt::heap;
/// Bx is a Box-like container, but differs in several important ways:
///
/// It can contain "no value" (like Option<Box>), but trying to derefence
/// such a box will panic. Use bx_set to set a value inside the bx.
///
/// You can get an immutable reference to the Bx's interior, if you promise
/// not to use it when no value is in the Bx (bx_unsafe_ref).
/// There is no way to get a mutable reference to Bx's interior.
///
/// There is no memory overhead (i e, same as Box).
///
/// Bx does not implement the magic DerefMove trait.
pub struct Bx<T>(ptr::Unique<T>);
impl<T> Bx<T> {
fn int_ref(&self) -> (*mut T, bool) {
unsafe {
let p: usize = mem::transmute(self.0.get());
(mem::transmute(p & !1), (p & 1) == 0)
}
}
fn size_align() -> (usize, usize) {
let mut a = mem::align_of::<T>();
if a < 2 { a = 2; }
let s = mem::size_of::<T>();
debug_assert!(s > 0); // Not sure if ZST would lead to problems, better safe than sorry
(s, a)
}
/// Allows for graceful OOM handling. Normally, use new() instead.
pub fn new_opt() -> Result<Bx<T>, ()> {
unsafe {
let (s, a) = Bx::<T>::size_align();
let p = heap::allocate(s, a);
if p == ptr::null_mut() { Err(()) }
else { Ok(Bx(ptr::Unique::new(p.offset(1) as *mut _))) }
}
}
pub fn new() -> Bx<T> {
Bx::new_opt().unwrap()
}
pub fn bx_set(&mut self, t: T) {
self.bx_clear();
unsafe {
let (p, set) = self.int_ref();
debug_assert!(!set);
self.0 = ptr::Unique::new(p);
ptr::copy_nonoverlapping(&t, self.0.get_mut(), 1);
mem::forget(t);
}
}
pub fn bx_clear(&mut self) {
let (p, set) = self.int_ref();
if !set { return; }
unsafe {
intrinsics::drop_in_place(p);
let p2 = p as *mut u8;
self.0 = ptr::Unique::new(p2.offset(1) as *mut _);
}
}
// This function is highly unsafe, but also highly useful.
// The rules are, you promise to only derefence this reference
// whenever you have called bx_set and before the Bx has been
// destroyed. Avoid bx_clear unless you know exactly what you're doing.
pub unsafe fn bx_unsafe_ref(&self) -> &'static T {
mem::transmute(self.int_ref().0)
}
}
impl<T> ops::Deref for Bx<T> {
type Target = T;
fn deref(&self) -> &T {
let (p, set) = self.int_ref();
if !set { panic!("Dereferencing an unset Bx") }
unsafe { mem::transmute(p) }
}
}
impl<T> Drop for Bx<T> {
fn drop(&mut self) {
unsafe {
self.bx_clear();
let (p, set) = self.int_ref();
debug_assert!(!set);
let (s, a) = Bx::<T>::size_align();
heap::deallocate(p as *mut u8, s, a);
}
}
}
#[test]
fn not_set() {
let z: Bx<String> = Bx::new();
let (_, b) = z.int_ref();
assert_eq!(b, false);
}
#[test]
fn set_and_clear_simple() {
let mut z: Bx<u32> = Bx::new();
let (p1, b) = z.int_ref();
assert_eq!(b, false);
z.bx_set(57u32);
let (p2, b) = z.int_ref();
assert_eq!(p1, p2);
assert_eq!(b, true);
z.bx_clear();
let (p3, b) = z.int_ref();
assert_eq!(p1, p3);
assert_eq!(b, false);
}
#[test]
fn test_drop() {
use std::cell::Cell;
let dropped = Cell::new(0u32);
struct Dummy<'a>(&'a Cell<u32>);
impl<'a> Drop for Dummy<'a> {
fn drop(&mut self) { self.0.set(self.0.get()+1) }
}
{
let mut m = Bx::<Dummy>::new();
assert_eq!(dropped.get(), 0);
m.bx_set(Dummy(&dropped));
assert_eq!(dropped.get(), 0);
m.bx_clear();
assert_eq!(dropped.get(), 1);
m.bx_set(Dummy(&dropped));
assert_eq!(dropped.get(), 1);
}
assert_eq!(dropped.get(), 2);
}
#[test]
fn test_deref() {
let mut m: Bx<u32> = Bx::new();
let ur = unsafe { m.bx_unsafe_ref() };
m.bx_set(83);
let sr = &*m;
assert_eq!(sr as *const u32, ur as *const _);
assert_eq!(*sr, 83);
assert_eq!(*ur, 83);
}
#[cfg(test)]
mod example {
use super::Bx;
use std::cell::Cell;
struct Car { wheels: Vec<Wheel>, speed: Cell<u32> }
struct Wheel(&'static Car);
impl Car {
// There is no way Wheel could reference an dropped car:
// Bx (unlike Box) does not allow DerefMove behaviour (Car cannot move out of the Bx)
// Destroying Bx or calling bx_clear will destroy both the Car and its Wheels
pub fn new() -> Bx<Car> {
let mut bx: Bx<Car> = Bx::new();
let cref = unsafe { bx.bx_unsafe_ref() };
let car = Car {
speed: Cell::new(0),
wheels: vec![Wheel(cref), Wheel(cref), Wheel(cref), Wheel(cref)]
};
bx.bx_set(car);
bx
}
// Some dummy functions to make use of references in both directions
pub fn accelerate(&self) { for w in &self.wheels { w.accelerate() }}
pub fn get_speed(&self) -> u32 { self.speed.get() }
}
impl Wheel { fn accelerate(&self) { self.0.speed.set(self.0.speed.get() + 1) }}
#[test]
fn test_car() {
let c = Car::new();
c.accelerate();
assert_eq!(c.get_speed(), 4);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment