Skip to content

Instantly share code, notes, and snippets.

@diwic
Created July 25, 2015 05:50
Show Gist options
  • Save diwic/3874dd729a5bf7ba17fa to your computer and use it in GitHub Desktop.
Save diwic/3874dd729a5bf7ba17fa to your computer and use it in GitHub Desktop.
use std::{mem, ops};
/// 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 a slight memory overhead, but only in the stable version
/// (still less than Rc, though). The nightly version has no overhead
/// (i e, same as Box).
///
/// Bx does not implement the magic DerefMove trait.
pub struct Bx<T>(Box<Option<T>>);
impl<T> Bx<T> {
pub fn new() -> Bx<T> {
Bx(Box::new(None))
}
pub fn bx_set(&mut self, t: T) {
*self.0 = Some(t);
}
pub fn bx_clear(&mut self) {
self.0.take();
}
/// 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 {
let p: &T;
// Note: This stuff is quite hairy. Given that it's only a
// static pointer calculation, there should be an easy and simple
// way to do it for all cases, right?
if let &Some(ref s) = &*self.0 { p = s; }
else if mem::size_of::<T>() == mem::size_of::<Option<T>>() {
p = mem::transmute(&*self.0);
}
else {
let m = self as *const _ as *mut Bx<T>;
*(*m).0 = Some(mem::uninitialized());
p = (*m).0.as_ref().unwrap();
mem::forget((*m).0.take());
}
mem::transmute(p)
}
}
impl<T> ops::Deref for Bx<T> {
type Target = T;
fn deref(&self) -> &T {
self.0.as_ref().unwrap()
}
}
#[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