Created
July 25, 2015 05:50
-
-
Save diwic/3874dd729a5bf7ba17fa to your computer and use it in GitHub Desktop.
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
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