Skip to content

Commit e3926f9

Browse files
Various improvements as suggested by @HeroicKatora
It features: - enhancing `Vec::reserve_uninit` to a more general API for accessing with the backing buffer / capacity of a Vec; - Rename `idx` to `.get_out` - (Ab)use `.copy_from_slice()` in read implementations to use less unsafe TODO: - The iterator API, - Documentation Co-Authored-By: Andreas Molzer <[email protected]>
1 parent 62a5a14 commit e3926f9

File tree

12 files changed

+417
-134
lines changed

12 files changed

+417
-134
lines changed

.cargo/config

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
rustdocflags = ["-C", "opt-level=2"]

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[lib]
2+
crate-type = ["rlib", "cdylib"]
3+
14
[package]
25
name = "uninit"
36
version = "0.1.0"
@@ -20,6 +23,7 @@ require_unsafe_in_body = "0.2.0"
2023
[features]
2124
nightly = []
2225
specialization = ["nightly"]
26+
const_generics = ["nightly"]
2327
chain = []
2428

2529
default = []

src/extension_traits/as_out.rs

+65-16
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod private {
1111
impl<T : ?Sized> Is for T { type Eq = T; }
1212
}
1313

14-
/// Helper / extension trait to convert a `&mut _` into a `&out T` by calling
14+
/// Extension trait to convert a `&mut _` into a `&out T` by calling
1515
/// `.as_out::<T>()` on it.
1616
///
1717
/// By autoref, this means that you can even just extract a `&out T` reference
@@ -129,8 +129,55 @@ impl<'out, T : 'out> AsOut<[T]> for &'out mut [ManuallyDrop<T>] {
129129
}
130130
}
131131

132-
macro_rules! impl_arrays {( $($N:tt)* ) => ($(
133-
impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>; $N] {
132+
#[cfg(not(feature = "const_generics"))]
133+
const _: () = {
134+
macro_rules! impl_arrays {( $($N:tt)* ) => ($(
135+
impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>; $N] {
136+
type Out = OutSlice<'out, T>;
137+
138+
#[inline]
139+
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
140+
-> OutSlice<'out, T>
141+
{
142+
From::from(&mut self[..])
143+
}
144+
}
145+
impl<'out, T : 'out> AsOut<[T]> for &'out mut [T; $N]
146+
where
147+
T : Copy,
148+
{
149+
type Out = OutSlice<'out, T>;
150+
151+
#[inline]
152+
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
153+
-> OutSlice<'out, T>
154+
{
155+
From::from(&mut self[..])
156+
}
157+
}
158+
)*)}
159+
160+
impl_arrays! {
161+
0 1 2 3 4 5 6 7
162+
8 9 10 11 12 13 14 15
163+
16 17 18 19 20 21 22 23
164+
24 25 26 27 28 29 30 31
165+
32 33 34 35 36 37 38 39
166+
40 41 42 43 44 45 46 47
167+
48 49 50 51 52 53 54 55
168+
56 57 58 59 60 61 62 63
169+
64
170+
128
171+
256
172+
512
173+
1024
174+
}
175+
};
176+
177+
#[cfg(feature = "const_generics")]
178+
const _: () = {
179+
#[doc(cfg(feature = "const_generics"))]
180+
impl<'out, T : 'out, const N: usize> AsOut<[T]> for &'out mut [MaybeUninit<T>; N] {
134181
type Out = OutSlice<'out, T>;
135182

136183
#[inline]
@@ -140,16 +187,18 @@ macro_rules! impl_arrays {( $($N:tt)* ) => ($(
140187
From::from(&mut self[..])
141188
}
142189
}
143-
)*)}
144-
145-
impl_arrays! {
146-
0 1 2 3 4 5 6 7 8
147-
9 10 11 12 13 14 15 16
148-
17 18 19 20 21 22 23 24
149-
25 26 27 28 29 30 31 32
150-
33 34 35 36 37 38 39 40
151-
41 42 43 44 45 46 47 48
152-
49 50 51 52 53 54 55 56
153-
57 58 59 60 61 62 63 64
154-
128 256 512 1024
155-
}
190+
#[doc(cfg(feature = "const_generics"))]
191+
impl<'out, T : 'out, const N: usize> AsOut<[T]> for &'out mut [T; N]
192+
where
193+
T : Copy,
194+
{
195+
type Out = OutSlice<'out, T>;
196+
197+
#[inline]
198+
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
199+
-> OutSlice<'out, T>
200+
{
201+
From::from(&mut self[..])
202+
}
203+
}
204+
};

src/extension_traits/boxed.rs

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
use_prelude!();
2+
3+
use self::private::Sealed;
4+
mod private { pub trait Sealed : Sized {} }
5+
impl<T> Sealed for Box<MaybeUninit<T>> {}
6+
7+
/// Extension trait for uninitalized `Box` allocations and
8+
/// the optimized delayed-initialization pattern.
9+
///
10+
/// # Optimized in-place heap initialization
11+
///
12+
/// The `Box::uninit().init(...)` delayed-initialization pattern is suprisingly
13+
/// effective in helping the optimizer inline the creation of the value directly
14+
/// into the heap.
15+
///
16+
/// - In other words, this bundles [`::copyless`](https://docs.rs/copyless)
17+
/// functionality.
18+
///
19+
/// - For those wondering why `Box::new(...)` could not be made as efficient,
20+
/// the answer lies in temporaries: the `...` temporary when calling
21+
/// `Box::new()` is created _before_ attempting the allocation, and given
22+
/// that this allocation can fail / have side-effects, the optimizer is not
23+
/// allowed to reorder the creation of the temporary after the allocation,
24+
/// since it can change the semantics of the code for these corner (but
25+
/// not unreachable) cases. It is hence illegal for the optimizer to inline
26+
/// the creation of `...` directly into the heap.
27+
///
28+
/// Whereas `Box::uninit().init(...)` only creates the temporary after
29+
/// the allocation attempted in `uninit()` has succeeded, at which point
30+
/// it should be trivial for the optimizer to inline its creation directly
31+
/// into the heap.
32+
///
33+
/// - Note, however, that this property cannot be guaranteed from a library
34+
/// perspective; for instance, **the heap-inlined initialization does not
35+
/// seem to happen when the optimization level (`opt-level`) is less than
36+
/// `2`. Inversely, the author has observed that the heap-inlined
37+
/// initialization does seem to kick in when compiling with `-C
38+
/// opt-level=2`** (or `3`), _e.g._, when running on `--release`.
39+
///
40+
///
41+
/// ### Example
42+
///
43+
/// ```rust
44+
/// use ::uninit::prelude::*;
45+
///
46+
/// let ft: Box<u8> = Box::uninit().init(42);
47+
/// assert_eq!(*ft, 42);
48+
/// ```
49+
///
50+
/// This optimization can even allow creating arrays too big to fit in the
51+
/// stack.
52+
///
53+
/// - For instance, the following implementation panics:
54+
///
55+
/// ```rust,should_panic
56+
/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]>
57+
/// {
58+
/// // This can panic because of the `[0; 10_000_000]` stack
59+
/// // temporary overflowing the stack.
60+
/// Box::new([0; 10_000_000])
61+
/// }
62+
/// # println!("Address: {:p}", alloc_big_boxed_array());
63+
/// ```
64+
///
65+
/// - Whereas the following one does not
66+
/// (doc-tested with `RUSTDOCFLAGS=-Copt-level=2`):
67+
///
68+
/// ```rust
69+
/// # use ::uninit::prelude::*;
70+
/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]>
71+
/// {
72+
/// // But this works fine, since there is no stack temporary!
73+
/// Box::uninit().init([0; 10_000_000])
74+
/// }
75+
/// # println!("Address: {:p}", alloc_big_boxed_array());
76+
/// ```
77+
///
78+
/// # Handling allocation failure
79+
///
80+
/// A neat side-effect of this implementation is to expose the intermediate
81+
/// state of `Box::try_alloc()`, which yields an `Option<Box<MaybeUninit<T>>>`
82+
/// depending on whether the attempted allocation succeeded or not.
83+
///
84+
/// ### Example
85+
///
86+
/// ```rust,no_run
87+
/// use ::uninit::prelude::*;
88+
///
89+
/// let buf: Box<[u8; ::core::i32::MAX as _]> = match Box::try_alloc() {
90+
/// | Some(uninit) => uninit.init([0; ::core::i32::MAX as _]),
91+
/// | None => {
92+
/// panic!("Failed to allocate 2GB of memory");
93+
/// }
94+
/// };
95+
/// # let _ = buf;
96+
/// ```
97+
impl<T> BoxUninit for Box<MaybeUninit<T>> {
98+
type T = T;
99+
100+
/// Idiomatic allocation-failure unwrapping of [`BoxUninit::try_alloc`]`()`.
101+
#[inline]
102+
fn uninit ()
103+
-> Box<MaybeUninit<T>>
104+
{
105+
let layout = alloc::Layout::new::<T>();
106+
if let Some(it) = Self::try_alloc() { it } else {
107+
alloc::handle_alloc_error(layout);
108+
}
109+
}
110+
111+
/// Attempts to `Box`-allocate memory for `T`, without initializing it.
112+
///
113+
/// Returns `None` when the allocation fails.
114+
#[inline]
115+
fn try_alloc ()
116+
-> Option<Box<MaybeUninit<T>>>
117+
{Some({
118+
if ::core::mem::size_of::<T>() == 0 {
119+
Box::new(MaybeUninit::uninit())
120+
} else {
121+
unsafe {
122+
let layout = alloc::Layout::new::<T>();
123+
Self::from_raw(
124+
ptr::NonNull::<T>::new(alloc::alloc(layout).cast())?
125+
.as_ptr()
126+
.cast()
127+
128+
)
129+
}
130+
}
131+
})}
132+
133+
/// Safely initialize a `Box::MaybeUninit<T>` by providing a `value: T`
134+
/// (that can be inlined into the `Box`), and safely return the ergonomic
135+
/// `Box<T>` witness of that initialization.
136+
#[inline(always)]
137+
fn init (mut self: Box<MaybeUninit<T>>, value: T)
138+
-> Box<T>
139+
{
140+
unsafe {
141+
self.as_mut_ptr().write(value);
142+
Box::from_raw(Box::into_raw(self).cast())
143+
}
144+
}
145+
}
146+
/// Extension trait for uninitalized `Box` allocations and
147+
/// the optimized delayed-initialization pattern.
148+
pub
149+
trait BoxUninit : Sealed {
150+
type T;
151+
fn uninit ()
152+
-> Self
153+
;
154+
fn try_alloc ()
155+
-> Option<Self>
156+
;
157+
fn init (self, value: Self::T)
158+
-> Box<Self::T>
159+
;
160+
}

src/extension_traits/manually_drop_mut.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ::core::mem::ManuallyDrop;
22

33
#[cfg(doc)] use crate::Out;
44

5-
/// An extension trait providing a cast to the [`ManuallyDrop`] type.
5+
/// Extension trait providing a cast to the [`ManuallyDrop`] type.
66
///
77
/// This is useful if you want to use an [`Out`] reference to something that
88
/// is not `Copy` (potentially because it has drop glue, in which case you

src/extension_traits/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ pub use self::as_out::{
33
};
44
mod as_out;
55

6+
pub use self::boxed::{
7+
BoxUninit,
8+
};
9+
mod boxed;
10+
611
pub use self::manually_drop_mut::{
712
ManuallyDropMut,
813
};
@@ -15,6 +20,6 @@ mod maybe_uninit;
1520

1621
pub use self::vec::{
1722
VecExtendFromReader,
18-
VecReserveUninit,
23+
VecAllocation,
1924
};
2025
mod vec;

0 commit comments

Comments
 (0)