Created
July 24, 2015 21:55
-
-
Save diwic/4b86a6ec7bd4ccde94eb 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
#![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