Skip to content

Instantly share code, notes, and snippets.

@randomPoison
Last active October 3, 2016 18:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save randomPoison/ce2f80532c107ba3e9b955abdbda1e0e to your computer and use it in GitHub Desktop.
Save randomPoison/ce2f80532c107ba3e9b955abdbda1e0e to your computer and use it in GitHub Desktop.
Additional cell types for all sorts of irresponsible usage.
//! More shareable, mutable containers.
//!
//! [`Cell<T>`][cell] and [`RefCell<T>`][refcell] are a good start, but they
//! don't represent all the forms of interior mutability that developers might
//! need. This crate provides a more comprehensive suite of cell types to cover
//! the cases not solved by the standard library. For more information on cells
//! and interior mutability in general please see [the std::cell module's
//! documentation][std::cell].
//!
//! # When Should You Use Which Cell?
//!
//! Here's a quick breakdown of each cell type and when it would be helpful:
//!
//! ### Use an `InitCell<T>` when:
//!
//! - You have a non-optional value that can't be initialized when the object
//! is created.
//! - You want to ensure that the value is never accessed in an unitialized
//! state.
//! - You don't need the dynamically checked borrow rules of a
//! [`RefCell<T>`][refcell]
//! - You have a thread-local static that needs to be lazily initialzed at
//! startup, but you want to access it without checking if it's initialized.
//!
//! ### Use an `AtomicInitCell<T>` when:
//!
//! - You have a static that needs to be lazily initialized, but you want to be
//! able to access the data without checking if it's initialized.
//! - You want to use a thread-safe `InitCell<T>`.
//!
//! ### Use an `AtomicCell<T>` when:
//!
//! - You want to use a thread-safe [`Cell<T>`][cell].
//!
//! ### Use an `AtomicRefCell<T>` when:
//!
//! - You want to use a [`RefCell<T>`][refcell] but need to to be thread-safe.
//! - You want an [`RwLock<T>`][rwlock] that panics instead of blocking.
//!
//! [std::cell]: https://doc.rust-lang.org/std/cell/index.html
//! [cell]: https://doc.rust-lang.org/std/cell/struct.Cell.html
//! [refcell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
//! [rwlock]: https://doc.rust-lang.org/std/sync/struct.RwLock.html
#![feature(const_fn)]
use std::cell::UnsafeCell;
use std::ops::{Deref, DerefMut};
/// Cell that allows a value to be lazily initialized once.
///
///
pub struct InitCell<T>(UnsafeCell<Option<T>>);
impl<T> InitCell<T> {
/// Create a new, uninitialized `InitCell<T>`.
pub const fn new() -> InitCell<T> {
InitCell(UnsafeCell::new(None))
}
/// Initialize the cell with the specified value.
///
/// # Panics
///
/// - If the cell has already been initialized.
pub fn init(&self, value: T) {
// It's safe to take a mutable reference to the data in here because
// reasons:
//
// - If the data hasn't been initialized, then attempts to take a
// reference to the data would have panicked, so we can assume there
// are no references to the data.
// - If the data has been initialized, then we're about to panic anyway
// so whatever.
let data = unsafe { &mut *self.0.get() };
assert!(data.is_none(), "Cannot initialize InitCell more than once");
*data = Some(value);
}
/// Get a reference to the data if the cell has been initialized.
pub fn get(&self) -> Option<&T> {
let data = unsafe { &*self.0.get() };
data.as_ref()
}
/// Get a mutable reference to the data if the cell has been initialized.
pub fn get_mut(&mut self) -> Option<&mut T> {
let data = unsafe { &mut *self.0.get() };
data.as_mut()
}
}
impl<T> Deref for InitCell<T> {
type Target = T;
fn deref(&self) -> &T {
self.get().expect("Cannot deref `InitCell` if it hasn't been initialized")
}
}
impl<T> DerefMut for InitCell<T> {
fn deref_mut(&mut self) -> &mut T {
self.get_mut().expect("Cannot deref `InitCell` if it hasn't been initialized")
}
}
unsafe impl<T: Send> Send for InitCell<T> {}
fn main() {
thread_local! {
static STATIC_DATA: InitCell<String> = InitCell::new();
}
// Normal usage of `InitCell<T>`.
let cell = InitCell::<usize>::new();
println!("cell before init: {:?}", cell.get());
cell.init(10);
println!("cell after init: {:?}", cell.get());
// Use `InitCell<T>` in a static.
STATIC_DATA.with(|static_data| static_data.init("foo".into()));
// STATIC_DATA.with(|static_data| println!("STATIC_DATA: {:?}", static_data));
let empty_cell = InitCell::<f32>::new();
println!("empty cell: {:?}", &*empty_cell);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment