diff --git a/.travis.yml b/.travis.yml index 5051a49..824c4f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,9 @@ matrix: - rust: nightly env: - NODROP_FEATURES='use_needs_drop' + - rust: nightly + env: + - NODROP_FEATURES='use_union' branches: only: - master diff --git a/nodrop-union/Cargo.toml b/nodrop-union/Cargo.toml new file mode 100644 index 0000000..1ee2cfa --- /dev/null +++ b/nodrop-union/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nodrop-union" +version = "0.1.8" +authors = ["bluss"] + +license = "MIT/Apache-2.0" + +description = "A wrapper type to inhibit drop (destructor). Implementation crate for nodrop, the untagged unions implementation (which is unstable / requires nightly) as of this writing." +documentation = "http://bluss.github.io/arrayvec/doc/nodrop" +repository = "https://github.com/bluss/arrayvec" + +keywords = ["container", "drop", "no_std"] + diff --git a/nodrop-union/src/lib.rs b/nodrop-union/src/lib.rs new file mode 100644 index 0000000..a7fc9e2 --- /dev/null +++ b/nodrop-union/src/lib.rs @@ -0,0 +1,135 @@ +//! +//! The **nodrop** crate has the following cargo feature flags: +//! +//! - `std` +//! - Optional, enabled by default +//! - Requires Rust 1.6 *to disable* +//! - Use libstd +//! - `use_needs_drop` +//! - Optional +//! - Requires nightly channel. +//! - Use `needs_drop` to skip overwriting if not necessary +//! - `use_union` +//! - Optional +//! - Requires nightly channel +//! - Using untagged union, finally we have an implementation of `NoDrop` without hacks, +//! for example the fact that `NoDrop` never has a destructor anymore. +//! + +#![feature(untagged_unions)] + +#![cfg_attr(not(test), no_std)] +#[cfg(not(test))] +extern crate core as std; + +use std::ops::{Deref, DerefMut}; + +#[allow(unions_with_drop_fields)] +union UnionFlag { + value: T, +} + +pub struct NoDrop(UnionFlag); + +impl NoDrop { + /// Create a new **NoDrop**. + #[inline] + pub fn new(value: T) -> Self { + NoDrop(UnionFlag { value: value }) + } + + /// Extract the inner value. + /// + /// Once extracted, the value can of course drop again. + #[inline] + pub fn into_inner(self) -> T { + unsafe { + self.0.value + } + } +} + +impl Deref for NoDrop { + type Target = T; + + // Use type invariant, always initialized + #[inline] + fn deref(&self) -> &T { + unsafe { + &self.0.value + } + } +} + +impl DerefMut for NoDrop { + // Use type invariant, always initialized + #[inline] + fn deref_mut(&mut self) -> &mut T { + unsafe { + &mut self.0.value + } + } +} + +#[cfg(test)] +mod tests { + use super::NoDrop; + use std::mem; + + #[test] + fn test_drop() { + use std::cell::Cell; + + let flag = &Cell::new(0); + + struct Bump<'a>(&'a Cell); + + impl<'a> Drop for Bump<'a> { + fn drop(&mut self) { + let n = self.0.get(); + self.0.set(n + 1); + } + } + + { + let _ = NoDrop::new([Bump(flag), Bump(flag)]); + } + assert_eq!(flag.get(), 0); + + // test something with the nullable pointer optimization + flag.set(0); + + { + let mut array = NoDrop::new(Vec::new()); + array.push(vec![Bump(flag)]); + array.push(vec![Bump(flag), Bump(flag)]); + array.push(vec![]); + array.push(vec![Bump(flag)]); + drop(array.pop()); + assert_eq!(flag.get(), 1); + drop(array.pop()); + assert_eq!(flag.get(), 1); + drop(array.pop()); + assert_eq!(flag.get(), 3); + } + + // last one didn't drop. + assert_eq!(flag.get(), 3); + + flag.set(0); + { + let array = NoDrop::new(Bump(flag)); + array.into_inner(); + assert_eq!(flag.get(), 1); + } + assert_eq!(flag.get(), 1); + } + + #[test] + fn test_size_of() { + assert!(mem::size_of::>() == mem::size_of::<&i32>()); + assert!(mem::size_of::>>() == mem::size_of::>()); + // No non-nullable pointer optimization! + assert!(mem::size_of::>>() > mem::size_of::>()); + } +} diff --git a/nodrop/Cargo.toml b/nodrop/Cargo.toml index 6f29aa2..69bbdb9 100644 --- a/nodrop/Cargo.toml +++ b/nodrop/Cargo.toml @@ -22,6 +22,13 @@ std = ["odds/std"] # Use `needs_drop` to skip overwriting if not necessary use_needs_drop = [] +# Optional, nightly channel +use_union = ["nodrop-union"] + [dependencies.odds] version = "0.2.12" default-features = false + +[dependencies.nodrop-union] +path = "../nodrop-union" +optional = true diff --git a/nodrop/src/lib.rs b/nodrop/src/lib.rs index e9ea1e3..1453637 100644 --- a/nodrop/src/lib.rs +++ b/nodrop/src/lib.rs @@ -9,6 +9,11 @@ //! - Optional //! - Requires nightly channel. //! - Use `needs_drop` to skip overwriting if not necessary +//! - `use_union` +//! - Optional +//! - Requires nightly channel +//! - Using untagged union, finally we have an implementation of `NoDrop` without hacks, +//! for example the fact that `NoDrop` never has a destructor anymore. //! #![cfg_attr(feature="use_needs_drop", feature(core_intrinsics))] @@ -17,150 +22,165 @@ #[cfg(not(any(test, feature="std")))] extern crate core as std; +#[cfg(not(feature = "use_union"))] extern crate odds; -use odds::debug_assert_unreachable; +#[cfg(feature = "use_union")] +extern crate nodrop_union as imp; -use std::ops::{Deref, DerefMut}; -use std::ptr; -use std::mem; +pub use imp::NoDrop; -/// repr(u8) - Make sure the non-nullable pointer optimization does not occur! -#[repr(u8)] -enum Flag { - Alive(T), - Dropped, -} -/// A type holding **T** that will not call its destructor on drop -pub struct NoDrop(Flag); +#[cfg(not(feature = "use_union"))] +mod imp { + use std; + use odds::debug_assert_unreachable; + use std::ptr; + use std::mem; + use std::ops::{Deref, DerefMut}; -impl NoDrop { - /// Create a new **NoDrop**. + /// repr(u8) - Make sure the non-nullable pointer optimization does not occur! + #[repr(u8)] + enum Flag { + Alive(T), + Dropped, + } + + + /// A type holding **T** that will not call its destructor on drop + pub struct NoDrop(Flag); + + impl NoDrop { + /// Create a new **NoDrop**. + #[inline] + pub fn new(value: T) -> NoDrop { + NoDrop(Flag::Alive(value)) + } + + /// Extract the inner value. + /// + /// Once extracted, the value can of course drop again. + #[inline] + pub fn into_inner(mut self) -> T { + let inner = unsafe { + ptr::read(&mut *self) + }; + // skip Drop, so we don't even have to overwrite + mem::forget(self); + inner + } + } + + #[cfg(not(feature = "use_needs_drop"))] #[inline] - pub fn new(value: T) -> NoDrop { - NoDrop(Flag::Alive(value)) + fn needs_drop() -> bool { + true } - /// Extract the inner value. - /// - /// Once extracted, the value can of course drop again. + #[cfg(feature = "use_needs_drop")] #[inline] - pub fn into_inner(mut self) -> T { - let inner = unsafe { - ptr::read(&mut *self) - }; - // skip Drop, so we don't even have to overwrite - mem::forget(self); - inner + fn needs_drop() -> bool { + unsafe { + std::intrinsics::needs_drop::() + } } -} -#[cfg(not(feature = "use_needs_drop"))] -#[inline] -fn needs_drop() -> bool { - true -} - -#[cfg(feature = "use_needs_drop")] -#[inline] -fn needs_drop() -> bool { - unsafe { - std::intrinsics::needs_drop::() - } -} - -impl Drop for NoDrop { - fn drop(&mut self) { - if needs_drop::() { - // inhibit drop - unsafe { - ptr::write(&mut self.0, Flag::Dropped); + impl Drop for NoDrop { + fn drop(&mut self) { + if needs_drop::() { + // inhibit drop + unsafe { + ptr::write(&mut self.0, Flag::Dropped); + } } } } -} -impl Deref for NoDrop { - type Target = T; + impl Deref for NoDrop { + type Target = T; - // Use type invariant, always Flag::Alive. - #[inline] - fn deref(&self) -> &T { - match self.0 { - Flag::Alive(ref inner) => inner, - _ => unsafe { debug_assert_unreachable() } + // Use type invariant, always Flag::Alive. + #[inline] + fn deref(&self) -> &T { + match self.0 { + Flag::Alive(ref inner) => inner, + _ => unsafe { debug_assert_unreachable() } + } } } -} -impl DerefMut for NoDrop { - // Use type invariant, always Flag::Alive. - #[inline] - fn deref_mut(&mut self) -> &mut T { - match self.0 { - Flag::Alive(ref mut inner) => inner, - _ => unsafe { debug_assert_unreachable() } + impl DerefMut for NoDrop { + // Use type invariant, always Flag::Alive. + #[inline] + fn deref_mut(&mut self) -> &mut T { + match self.0 { + Flag::Alive(ref mut inner) => inner, + _ => unsafe { debug_assert_unreachable() } + } } } + + #[cfg(test)] + #[test] + fn test_no_nonnullable_opt() { + // Make sure `Flag` does not apply the non-nullable pointer optimization + // as Option would do. + assert!(mem::size_of::>() > mem::size_of::<&i32>()); + assert!(mem::size_of::>>() > mem::size_of::>()); + assert!(mem::size_of::>>() > mem::size_of::>()); + } } #[cfg(test)] -#[test] -fn test_no_nonnullable_opt() { - // Make sure `Flag` does not apply the non-nullable pointer optimization - // as Option would do. - assert!(mem::size_of::>() > mem::size_of::<&i32>()); - assert!(mem::size_of::>>() > mem::size_of::>()); - assert!(mem::size_of::>>() > mem::size_of::>()); -} +mod tests { + use super::NoDrop; -#[cfg(test)] -#[test] -fn test_drop() { - use std::cell::Cell; + #[test] + fn test_drop() { + use std::cell::Cell; - let flag = &Cell::new(0); + let flag = &Cell::new(0); - struct Bump<'a>(&'a Cell); + struct Bump<'a>(&'a Cell); - impl<'a> Drop for Bump<'a> { - fn drop(&mut self) { - let n = self.0.get(); - self.0.set(n + 1); + impl<'a> Drop for Bump<'a> { + fn drop(&mut self) { + let n = self.0.get(); + self.0.set(n + 1); + } } - } - { - let _ = NoDrop::new([Bump(flag), Bump(flag)]); - } - assert_eq!(flag.get(), 0); + { + let _ = NoDrop::new([Bump(flag), Bump(flag)]); + } + assert_eq!(flag.get(), 0); - // test something with the nullable pointer optimization - flag.set(0); + // test something with the nullable pointer optimization + flag.set(0); - { - let mut array = NoDrop::new(Vec::new()); - array.push(vec![Bump(flag)]); - array.push(vec![Bump(flag), Bump(flag)]); - array.push(vec![]); - array.push(vec![Bump(flag)]); - drop(array.pop()); - assert_eq!(flag.get(), 1); - drop(array.pop()); - assert_eq!(flag.get(), 1); - drop(array.pop()); + { + let mut array = NoDrop::new(Vec::new()); + array.push(vec![Bump(flag)]); + array.push(vec![Bump(flag), Bump(flag)]); + array.push(vec![]); + array.push(vec![Bump(flag)]); + drop(array.pop()); + assert_eq!(flag.get(), 1); + drop(array.pop()); + assert_eq!(flag.get(), 1); + drop(array.pop()); + assert_eq!(flag.get(), 3); + } + + // last one didn't drop. assert_eq!(flag.get(), 3); - } - // last one didn't drop. - assert_eq!(flag.get(), 3); - - flag.set(0); - { - let array = NoDrop::new(Bump(flag)); - array.into_inner(); + flag.set(0); + { + let array = NoDrop::new(Bump(flag)); + array.into_inner(); + assert_eq!(flag.get(), 1); + } assert_eq!(flag.get(), 1); } - assert_eq!(flag.get(), 1); }