FEAT: Implement a "MaybeUninit" and use it conditionally
Use a build script to detect if we can use MaybeUninit or NoDrop. Enabling unstable features automatically is not ideal, but since it's a soundness issue we should do it. Use a MaybeUninit-like union on nightly when we can. We use a feature detection script in build.rs, so that we also go back to the fallback if the unstable feature changes in an unexpected way. We need to continue to use NoDrop for best working stable implementation, but we eagerly use our union solution where we can, currently only in nightlies. Rustc feature probe code written by Josh Stone (cuviper), taken from num-bigint.
This commit is contained in:
+5
-1
@@ -11,6 +11,8 @@ repository = "https://github.com/bluss/arrayvec"
|
||||
keywords = ["stack", "vector", "array", "data-structure", "no_std"]
|
||||
categories = ["data-structures", "no-std"]
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
[dependencies]
|
||||
nodrop = { version = "0.1.12", path = "nodrop", default-features = false }
|
||||
|
||||
@@ -37,12 +39,14 @@ harness = false
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
use_union = []
|
||||
serde-1 = ["serde"]
|
||||
|
||||
array-sizes-33-128 = []
|
||||
array-sizes-129-255 = []
|
||||
|
||||
# has no effect
|
||||
use_union = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["serde-1"]
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() {
|
||||
// we need to output *some* file to opt out of the default
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
detect_maybe_uninit();
|
||||
}
|
||||
|
||||
fn detect_maybe_uninit() {
|
||||
let has_unstable_union_with_md = probe(&maybe_uninit_code(true));
|
||||
if has_unstable_union_with_md {
|
||||
println!("cargo:rustc-cfg=has_manually_drop_in_union");
|
||||
println!("cargo:rustc-cfg=has_union_feature");
|
||||
return;
|
||||
}
|
||||
|
||||
let has_stable_union_with_md = probe(&maybe_uninit_code(false));
|
||||
if has_stable_union_with_md {
|
||||
println!("cargo:rustc-cfg=has_manually_drop_in_union");
|
||||
}
|
||||
}
|
||||
|
||||
// To guard against changes in this currently unstable feature, use
|
||||
// a detection tests instead of a Rustc version and/or date test.
|
||||
fn maybe_uninit_code(use_feature: bool) -> String {
|
||||
let feature = if use_feature { "#![feature(untagged_unions)]" } else { "" };
|
||||
|
||||
let code = "
|
||||
#![allow(warnings)]
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
#[derive(Copy)]
|
||||
pub union MaybeUninit<T> {
|
||||
empty: (),
|
||||
value: ManuallyDrop<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for MaybeUninit<T> where T: Copy
|
||||
{
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let value1 = MaybeUninit::<[i32; 3]> { empty: () };
|
||||
let value2 = MaybeUninit { value: ManuallyDrop::new([1, 2, 3]) };
|
||||
}
|
||||
";
|
||||
|
||||
|
||||
[feature, code].concat()
|
||||
}
|
||||
|
||||
/// Test if a code snippet can be compiled
|
||||
fn probe(code: &str) -> bool {
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
|
||||
let out_dir = env::var_os("OUT_DIR").expect("environment variable OUT_DIR");
|
||||
|
||||
let mut child = Command::new(rustc)
|
||||
.arg("--out-dir")
|
||||
.arg(out_dir)
|
||||
.arg("--emit=obj")
|
||||
.arg("-")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("rustc probe");
|
||||
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.expect("rustc stdin")
|
||||
.write_all(code.as_bytes())
|
||||
.expect("write rustc stdin");
|
||||
|
||||
child.wait().expect("rustc probe").success()
|
||||
}
|
||||
+18
-20
@@ -7,14 +7,6 @@
|
||||
//! - Optional, enabled by default
|
||||
//! - Use libstd; disable to use `no_std` instead.
|
||||
//!
|
||||
//! - `use_union`
|
||||
//! - Optional
|
||||
//! - Requires Rust nightly channel
|
||||
//! - Experimental: This flag uses nightly so it *may break* unexpectedly
|
||||
//! at some point; since it doesn't change API this flag may also change
|
||||
//! to do nothing in the future.
|
||||
//! - Use the unstable feature untagged unions for the internal implementation,
|
||||
//! which may have reduced space overhead
|
||||
//! - `serde-1`
|
||||
//! - Optional
|
||||
//! - Enable serialization for ArrayVec and ArrayString using serde 1.0
|
||||
@@ -28,13 +20,17 @@
|
||||
//!
|
||||
#![doc(html_root_url="https://docs.rs/arrayvec/0.4/")]
|
||||
#![cfg_attr(not(feature="std"), no_std)]
|
||||
extern crate nodrop;
|
||||
#![cfg_attr(has_union_feature, feature(untagged_unions))]
|
||||
|
||||
#[cfg(feature="serde-1")]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(not(feature="std"))]
|
||||
extern crate core as std;
|
||||
|
||||
#[cfg(not(has_manually_drop_in_union))]
|
||||
extern crate nodrop;
|
||||
|
||||
use std::cmp;
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
@@ -53,11 +49,14 @@ use std::fmt;
|
||||
#[cfg(feature="std")]
|
||||
use std::io;
|
||||
|
||||
#[cfg(not(feature="use_union"))]
|
||||
use nodrop::NoDrop;
|
||||
|
||||
#[cfg(feature="use_union")]
|
||||
use std::mem::ManuallyDrop as NoDrop;
|
||||
#[cfg(has_manually_drop_in_union)]
|
||||
mod maybe_uninit;
|
||||
#[cfg(not(has_manually_drop_in_union))]
|
||||
#[path="maybe_uninit_nodrop.rs"]
|
||||
mod maybe_uninit;
|
||||
|
||||
use maybe_uninit::MaybeUninit;
|
||||
|
||||
#[cfg(feature="serde-1")]
|
||||
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
||||
@@ -96,7 +95,7 @@ unsafe fn new_array<A: Array>() -> A {
|
||||
///
|
||||
/// ArrayVec can be converted into a by value iterator.
|
||||
pub struct ArrayVec<A: Array> {
|
||||
xs: NoDrop<A>,
|
||||
xs: MaybeUninit<A>,
|
||||
len: A::Index,
|
||||
}
|
||||
|
||||
@@ -133,7 +132,7 @@ impl<A: Array> ArrayVec<A> {
|
||||
/// ```
|
||||
pub fn new() -> ArrayVec<A> {
|
||||
unsafe {
|
||||
ArrayVec { xs: NoDrop::new(new_array()), len: Index::from(0) }
|
||||
ArrayVec { xs: MaybeUninit::uninitialized(), len: Index::from(0) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +516,6 @@ impl<A: Array> ArrayVec<A> {
|
||||
self.len = Index::from(length);
|
||||
}
|
||||
|
||||
|
||||
/// Create a draining iterator that removes the specified range in the vector
|
||||
/// and yields the removed items from start to end. The element range is
|
||||
/// removed even if the iterator is not consumed until the end.
|
||||
@@ -577,7 +575,7 @@ impl<A: Array> ArrayVec<A> {
|
||||
Err(self)
|
||||
} else {
|
||||
unsafe {
|
||||
let array = ptr::read(&*self.xs);
|
||||
let array = ptr::read(self.xs.ptr() as *const A);
|
||||
mem::forget(self);
|
||||
Ok(array)
|
||||
}
|
||||
@@ -606,7 +604,7 @@ impl<A: Array> Deref for ArrayVec<A> {
|
||||
#[inline]
|
||||
fn deref(&self) -> &[A::Item] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(self.xs.as_ptr(), self.len())
|
||||
slice::from_raw_parts(self.xs.ptr(), self.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -616,7 +614,7 @@ impl<A: Array> DerefMut for ArrayVec<A> {
|
||||
fn deref_mut(&mut self) -> &mut [A::Item] {
|
||||
let len = self.len();
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self.xs.as_mut_ptr(), len)
|
||||
slice::from_raw_parts_mut(self.xs.ptr_mut(), len)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,7 +630,7 @@ impl<A: Array> DerefMut for ArrayVec<A> {
|
||||
/// ```
|
||||
impl<A: Array> From<A> for ArrayVec<A> {
|
||||
fn from(array: A) -> Self {
|
||||
ArrayVec { xs: NoDrop::new(array), len: Index::from(A::capacity()) }
|
||||
ArrayVec { xs: MaybeUninit::from(array), len: Index::from(A::capacity()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
|
||||
use array::Array;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
/// A combination of ManuallyDrop and “maybe uninitialized”;
|
||||
/// this wraps a value that can be wholly or partially uninitialized;
|
||||
/// it also has no drop regardless of the type of T.
|
||||
#[derive(Copy)]
|
||||
pub union MaybeUninit<T> {
|
||||
empty: (),
|
||||
value: ManuallyDrop<T>,
|
||||
}
|
||||
// Why we don't use std's MaybeUninit on nightly? See the ptr method
|
||||
|
||||
impl<T> Clone for MaybeUninit<T> where T: Copy
|
||||
{
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
impl<T> MaybeUninit<T> {
|
||||
/// Create a new MaybeUninit with uninitialized interior
|
||||
pub unsafe fn uninitialized() -> Self {
|
||||
MaybeUninit { empty: () }
|
||||
}
|
||||
|
||||
/// Create a new MaybeUninit from the value `v`.
|
||||
pub fn from(v: T) -> Self {
|
||||
MaybeUninit { value: ManuallyDrop::new(v) }
|
||||
}
|
||||
|
||||
// Raw pointer casts written so that we don't reference or access the
|
||||
// uninitialized interior value
|
||||
|
||||
/// Return a raw pointer to the start of the interior array
|
||||
pub fn ptr(&self) -> *const T::Item
|
||||
where T: Array
|
||||
{
|
||||
// std MaybeUninit creates a &self.value reference here which is
|
||||
// not guaranteed to be sound in our case - we will partially
|
||||
// initialize the value, not always wholly.
|
||||
self as *const _ as *const T::Item
|
||||
}
|
||||
|
||||
/// Return a mut raw pointer to the start of the interior array
|
||||
pub fn ptr_mut(&mut self) -> *mut T::Item
|
||||
where T: Array
|
||||
{
|
||||
self as *mut _ as *mut T::Item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
use array::Array;
|
||||
use nodrop::NoDrop;
|
||||
use std::mem::uninitialized;
|
||||
|
||||
/// A combination of NoDrop and “maybe uninitialized”;
|
||||
/// this wraps a value that can be wholly or partially uninitialized.
|
||||
///
|
||||
/// NOTE: This is known to not be a good solution, but it's the one we have kept
|
||||
/// working on stable Rust. Stable improvements are encouraged, in any form,
|
||||
/// but of course we are waiting for a real, stable, MaybeUninit.
|
||||
pub struct MaybeUninit<T>(NoDrop<T>);
|
||||
// why don't we use ManuallyDrop here: It doesn't inhibit
|
||||
// enum layout optimizations that depend on T, and we support older Rust.
|
||||
|
||||
impl<T> MaybeUninit<T> {
|
||||
/// Create a new MaybeUninit with uninitialized interior
|
||||
pub unsafe fn uninitialized() -> Self {
|
||||
Self::from(uninitialized())
|
||||
}
|
||||
|
||||
/// Create a new MaybeUninit from the value `v`.
|
||||
pub fn from(v: T) -> Self {
|
||||
MaybeUninit(NoDrop::new(v))
|
||||
}
|
||||
|
||||
/// Return a raw pointer to the start of the interior array
|
||||
pub fn ptr(&self) -> *const T::Item
|
||||
where T: Array
|
||||
{
|
||||
&*self.0 as *const T as *const _
|
||||
}
|
||||
|
||||
/// Return a mut raw pointer to the start of the interior array
|
||||
pub fn ptr_mut(&mut self) -> *mut T::Item
|
||||
where T: Array
|
||||
{
|
||||
&mut *self.0 as *mut T as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user