Skip to content

Commit 2901223

Browse files
committed
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.
1 parent 8f8617b commit 2901223

File tree

5 files changed

+194
-21
lines changed

5 files changed

+194
-21
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ repository = "https://github.com/bluss/arrayvec"
1111
keywords = ["stack", "vector", "array", "data-structure", "no_std"]
1212
categories = ["data-structures", "no-std"]
1313

14+
[build-dependencies]
15+
1416
[dependencies]
1517
nodrop = { version = "0.1.12", path = "nodrop", default-features = false }
1618

@@ -37,12 +39,14 @@ harness = false
3739
[features]
3840
default = ["std"]
3941
std = []
40-
use_union = []
4142
serde-1 = ["serde"]
4243

4344
array-sizes-33-128 = []
4445
array-sizes-129-255 = []
4546

47+
# has no effect
48+
use_union = []
49+
4650
[package.metadata.docs.rs]
4751
features = ["serde-1"]
4852

build.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
use std::env;
3+
use std::io::Write;
4+
use std::process::{Command, Stdio};
5+
6+
fn main() {
7+
// we need to output *some* file to opt out of the default
8+
println!("cargo:rerun-if-changed=build.rs");
9+
10+
detect_maybe_uninit();
11+
}
12+
13+
fn detect_maybe_uninit() {
14+
let has_unstable_union_with_md = probe(&maybe_uninit_code(true));
15+
if has_unstable_union_with_md {
16+
println!("cargo:rustc-cfg=has_manually_drop_in_union");
17+
println!("cargo:rustc-cfg=has_union_feature");
18+
return;
19+
}
20+
21+
let has_stable_union_with_md = probe(&maybe_uninit_code(false));
22+
if has_stable_union_with_md {
23+
println!("cargo:rustc-cfg=has_manually_drop_in_union");
24+
}
25+
}
26+
27+
// To guard against changes in this currently unstable feature, use
28+
// a detection tests instead of a Rustc version and/or date test.
29+
fn maybe_uninit_code(use_feature: bool) -> String {
30+
let feature = if use_feature { "#![feature(untagged_unions)]" } else { "" };
31+
32+
let code = "
33+
#![allow(warnings)]
34+
use std::mem::ManuallyDrop;
35+
36+
#[derive(Copy)]
37+
pub union MaybeUninit<T> {
38+
empty: (),
39+
value: ManuallyDrop<T>,
40+
}
41+
42+
impl<T> Clone for MaybeUninit<T> where T: Copy
43+
{
44+
fn clone(&self) -> Self { *self }
45+
}
46+
47+
fn main() {
48+
let value1 = MaybeUninit::<[i32; 3]> { empty: () };
49+
let value2 = MaybeUninit { value: ManuallyDrop::new([1, 2, 3]) };
50+
}
51+
";
52+
53+
54+
[feature, code].concat()
55+
}
56+
57+
/// Test if a code snippet can be compiled
58+
fn probe(code: &str) -> bool {
59+
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
60+
let out_dir = env::var_os("OUT_DIR").expect("environment variable OUT_DIR");
61+
62+
let mut child = Command::new(rustc)
63+
.arg("--out-dir")
64+
.arg(out_dir)
65+
.arg("--emit=obj")
66+
.arg("-")
67+
.stdin(Stdio::piped())
68+
.spawn()
69+
.expect("rustc probe");
70+
71+
child
72+
.stdin
73+
.as_mut()
74+
.expect("rustc stdin")
75+
.write_all(code.as_bytes())
76+
.expect("write rustc stdin");
77+
78+
child.wait().expect("rustc probe").success()
79+
}

src/lib.rs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@
77
//! - Optional, enabled by default
88
//! - Use libstd; disable to use `no_std` instead.
99
//!
10-
//! - `use_union`
11-
//! - Optional
12-
//! - Requires Rust nightly channel
13-
//! - Experimental: This flag uses nightly so it *may break* unexpectedly
14-
//! at some point; since it doesn't change API this flag may also change
15-
//! to do nothing in the future.
16-
//! - Use the unstable feature untagged unions for the internal implementation,
17-
//! which may have reduced space overhead
1810
//! - `serde-1`
1911
//! - Optional
2012
//! - Enable serialization for ArrayVec and ArrayString using serde 1.0
@@ -28,13 +20,17 @@
2820
//!
2921
#![doc(html_root_url="https://docs.rs/arrayvec/0.4/")]
3022
#![cfg_attr(not(feature="std"), no_std)]
31-
extern crate nodrop;
23+
#![cfg_attr(has_union_feature, feature(untagged_unions))]
24+
3225
#[cfg(feature="serde-1")]
3326
extern crate serde;
3427

3528
#[cfg(not(feature="std"))]
3629
extern crate core as std;
3730

31+
#[cfg(not(has_manually_drop_in_union))]
32+
extern crate nodrop;
33+
3834
use std::cmp;
3935
use std::iter;
4036
use std::mem;
@@ -53,11 +49,14 @@ use std::fmt;
5349
#[cfg(feature="std")]
5450
use std::io;
5551

56-
#[cfg(not(feature="use_union"))]
57-
use nodrop::NoDrop;
5852

59-
#[cfg(feature="use_union")]
60-
use std::mem::ManuallyDrop as NoDrop;
53+
#[cfg(has_manually_drop_in_union)]
54+
mod maybe_uninit;
55+
#[cfg(not(has_manually_drop_in_union))]
56+
#[path="maybe_uninit_nodrop.rs"]
57+
mod maybe_uninit;
58+
59+
use maybe_uninit::MaybeUninit;
6160

6261
#[cfg(feature="serde-1")]
6362
use serde::{Serialize, Deserialize, Serializer, Deserializer};
@@ -96,7 +95,7 @@ unsafe fn new_array<A: Array>() -> A {
9695
///
9796
/// ArrayVec can be converted into a by value iterator.
9897
pub struct ArrayVec<A: Array> {
99-
xs: NoDrop<A>,
98+
xs: MaybeUninit<A>,
10099
len: A::Index,
101100
}
102101

@@ -133,7 +132,7 @@ impl<A: Array> ArrayVec<A> {
133132
/// ```
134133
pub fn new() -> ArrayVec<A> {
135134
unsafe {
136-
ArrayVec { xs: NoDrop::new(new_array()), len: Index::from(0) }
135+
ArrayVec { xs: MaybeUninit::uninitialized(), len: Index::from(0) }
137136
}
138137
}
139138

@@ -517,7 +516,6 @@ impl<A: Array> ArrayVec<A> {
517516
self.len = Index::from(length);
518517
}
519518

520-
521519
/// Create a draining iterator that removes the specified range in the vector
522520
/// and yields the removed items from start to end. The element range is
523521
/// removed even if the iterator is not consumed until the end.
@@ -577,7 +575,7 @@ impl<A: Array> ArrayVec<A> {
577575
Err(self)
578576
} else {
579577
unsafe {
580-
let array = ptr::read(&*self.xs);
578+
let array = ptr::read(self.xs.ptr() as *const A);
581579
mem::forget(self);
582580
Ok(array)
583581
}
@@ -606,7 +604,7 @@ impl<A: Array> Deref for ArrayVec<A> {
606604
#[inline]
607605
fn deref(&self) -> &[A::Item] {
608606
unsafe {
609-
slice::from_raw_parts(self.xs.as_ptr(), self.len())
607+
slice::from_raw_parts(self.xs.ptr(), self.len())
610608
}
611609
}
612610
}
@@ -616,7 +614,7 @@ impl<A: Array> DerefMut for ArrayVec<A> {
616614
fn deref_mut(&mut self) -> &mut [A::Item] {
617615
let len = self.len();
618616
unsafe {
619-
slice::from_raw_parts_mut(self.xs.as_mut_ptr(), len)
617+
slice::from_raw_parts_mut(self.xs.ptr_mut(), len)
620618
}
621619
}
622620
}
@@ -632,7 +630,7 @@ impl<A: Array> DerefMut for ArrayVec<A> {
632630
/// ```
633631
impl<A: Array> From<A> for ArrayVec<A> {
634632
fn from(array: A) -> Self {
635-
ArrayVec { xs: NoDrop::new(array), len: Index::from(A::capacity()) }
633+
ArrayVec { xs: MaybeUninit::from(array), len: Index::from(A::capacity()) }
636634
}
637635
}
638636

src/maybe_uninit.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
3+
use array::Array;
4+
use std::mem::ManuallyDrop;
5+
6+
/// A combination of ManuallyDrop and “maybe uninitialized”;
7+
/// this wraps a value that can be wholly or partially uninitialized;
8+
/// it also has no drop regardless of the type of T.
9+
#[derive(Copy)]
10+
pub union MaybeUninit<T> {
11+
empty: (),
12+
value: ManuallyDrop<T>,
13+
}
14+
// Why we don't use std's MaybeUninit on nightly? See the ptr method
15+
16+
impl<T> Clone for MaybeUninit<T> where T: Copy
17+
{
18+
fn clone(&self) -> Self { *self }
19+
}
20+
21+
impl<T> MaybeUninit<T> {
22+
/// Create a new MaybeUninit with uninitialized interior
23+
pub unsafe fn uninitialized() -> Self {
24+
MaybeUninit { empty: () }
25+
}
26+
27+
/// Create a new MaybeUninit from the value `v`.
28+
pub fn from(v: T) -> Self {
29+
MaybeUninit { value: ManuallyDrop::new(v) }
30+
}
31+
32+
// Raw pointer casts written so that we don't reference or access the
33+
// uninitialized interior value
34+
35+
/// Return a raw pointer to the start of the interior array
36+
pub fn ptr(&self) -> *const T::Item
37+
where T: Array
38+
{
39+
// std MaybeUninit creates a &self.value reference here which is
40+
// not guaranteed to be sound in our case - we will partially
41+
// initialize the value, not always wholly.
42+
self as *const _ as *const T::Item
43+
}
44+
45+
/// Return a mut raw pointer to the start of the interior array
46+
pub fn ptr_mut(&mut self) -> *mut T::Item
47+
where T: Array
48+
{
49+
self as *mut _ as *mut T::Item
50+
}
51+
}

src/maybe_uninit_nodrop.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
use array::Array;
3+
use nodrop::NoDrop;
4+
use std::mem::uninitialized;
5+
6+
/// A combination of NoDrop and “maybe uninitialized”;
7+
/// this wraps a value that can be wholly or partially uninitialized.
8+
///
9+
/// NOTE: This is known to not be a good solution, but it's the one we have kept
10+
/// working on stable Rust. Stable improvements are encouraged, in any form,
11+
/// but of course we are waiting for a real, stable, MaybeUninit.
12+
pub struct MaybeUninit<T>(NoDrop<T>);
13+
// why don't we use ManuallyDrop here: It doesn't inhibit
14+
// enum layout optimizations that depend on T, and we support older Rust.
15+
16+
impl<T> MaybeUninit<T> {
17+
/// Create a new MaybeUninit with uninitialized interior
18+
pub unsafe fn uninitialized() -> Self {
19+
Self::from(uninitialized())
20+
}
21+
22+
/// Create a new MaybeUninit from the value `v`.
23+
pub fn from(v: T) -> Self {
24+
MaybeUninit(NoDrop::new(v))
25+
}
26+
27+
/// Return a raw pointer to the start of the interior array
28+
pub fn ptr(&self) -> *const T::Item
29+
where T: Array
30+
{
31+
&*self.0 as *const T as *const _
32+
}
33+
34+
/// Return a mut raw pointer to the start of the interior array
35+
pub fn ptr_mut(&mut self) -> *mut T::Item
36+
where T: Array
37+
{
38+
&mut *self.0 as *mut T as *mut _
39+
}
40+
}
41+

0 commit comments

Comments
 (0)