Skip to content

Commit

Permalink
Remove ArrayType<MaybeUninit<T>> = [MaybeUninit<T>; N] bounds
Browse files Browse the repository at this point in the history
This makes it possible to call `Array::uninit` from `Array::try_from_fn`
without propagating this bound, both to that function and everything
else that calls it, including `try_from_iter` and the `FromIterator`
trait impl.

This bound can be useful in places where we need access to `N`, but in
the specific cae of `Array::uninit` it's effectively an implementation
detail which, when provided, made clippy happy, because it could see the
inner type was a `MaybeUninit` array.

The safety comments spell out the rationale for why this is sound, which
effectively boils down to "uninitialized memory requires no
initialization".
  • Loading branch information
tarcieri committed Jan 29, 2024
1 parent e5884fc commit 7deb1a3
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 16 deletions.
6 changes: 2 additions & 4 deletions src/from_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ where
where
F: FnMut(usize) -> Result<T, E>,
{
// SAFETY: an array of `MaybeUninit`s is always valid.
let mut array: Array<MaybeUninit<T>, U> = unsafe { MaybeUninit::uninit().assume_init() };
let mut array = Array::<MaybeUninit<T>, U>::uninit();
try_from_fn_erased(array.0.as_mut(), f)?;

// TODO(tarcieri): use `MaybeUninit::array_assume_init` when stable
// SAFETY: if we got here, every element of the array was initialized
Ok(unsafe { ptr::read(array.as_ptr().cast()) })
Ok(unsafe { array.assume_init() })
}
}

Expand Down
25 changes: 13 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ use core::{
cmp::Ordering,
fmt::{self, Debug},
hash::{Hash, Hasher},
mem::{ManuallyDrop, MaybeUninit},
mem::{self, ManuallyDrop, MaybeUninit},
ops::{Add, Deref, DerefMut, Index, IndexMut, Sub},
ptr,
slice::{self, Iter, IterMut},
Expand Down Expand Up @@ -370,21 +370,22 @@ where
}
}

impl<T, U, const N: usize> Array<MaybeUninit<T>, U>
impl<T, U> Array<MaybeUninit<T>, U>
where
U: ArraySize<ArrayType<MaybeUninit<T>> = [MaybeUninit<T>; N]>,
U: ArraySize,
{
/// Create an uninitialized array of [`MaybeUninit`]s for the given type.
pub const fn uninit() -> Self {
// SAFETY: `Self` is a `repr(transparent)` newtype for `[MaybeUninit<T>; N]`. It is safe
// to assume `[MaybeUninit<T>; N]` is "initialized" because there is no initialization state
// for a `MaybeUninit`: it's a type for representing potentially uninitialized memory (and
// in this case it's uninitialized).
pub const fn uninit() -> Array<MaybeUninit<T>, U> {
// SAFETY: `Array` is a `repr(transparent)` newtype for `[MaybeUninit<T>, N]`, i.e. an
// array of uninitialized memory mediated via the `MaybeUninit` interface.
//
// This is identical to how `core` implements `MaybeUninit::uninit_array`:
// <https://github.com/rust-lang/rust/blob/917f654/library/core/src/mem/maybe_uninit.rs#L350-L352>
// Calling `uninit().assume_init()` triggers the `clippy::uninit_assumed_init` lint, but
// as just mentioned the inner type we're "assuming init" for is `[MaybeUninit<T>, N]`,
// i.e. an array of uninitialized memory, which is always valid because definitionally no
// initialization is required of uninitialized memory.
// TODO(tarcieri): use `MaybeUninit::uninit_array` when stable
Self(unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() })
#[allow(clippy::uninit_assumed_init)]
Self(unsafe { MaybeUninit::uninit().assume_init() })
}

/// Extract the values from an array of `MaybeUninit` containers.
Expand All @@ -395,7 +396,7 @@ where
/// state.
pub unsafe fn assume_init(self) -> Array<T, U> {
// TODO(tarcieri): use `MaybeUninit::array_assume_init` when stable
Array(ptr::read(self.0.as_ptr().cast()))
mem::transmute_copy(&self)
}
}

Expand Down

0 comments on commit 7deb1a3

Please sign in to comment.