diff --git a/Cargo.toml b/Cargo.toml index 2024c8f9..dd997ffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ js-sys = { version = "0.3", optional = true } wasm-bindgen-test = "0.3.18" [features] +# Feature to enable the `array()` function, which requires Rust 1.51 or later. +array = [] # Implement std-only traits for getrandom::Error std = [] # Feature to enable fallback RDRAND-based implementation on x86/x86_64 @@ -49,5 +51,5 @@ rustc-dep-of-std = [ test-in-browser = [] [package.metadata.docs.rs] -features = ["std", "custom"] +features = ["array", "custom", "std"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/array.rs b/src/array.rs new file mode 100644 index 00000000..3c78aa5e --- /dev/null +++ b/src/array.rs @@ -0,0 +1,99 @@ +use crate::{getrandom_uninit, Error}; +use core::mem::MaybeUninit; + +mod private { + pub trait Sealed {} +} + +/// A type supported by [`getrandom::array`](crate::getrandom::array). +#[cfg_attr(docsrs, doc(cfg(feature = "array")))] +pub unsafe trait ArrayItem: private::Sealed {} + +impl private::Sealed for u8 {} +unsafe impl ArrayItem for u8 {} + +impl private::Sealed for [I; N] {} +#[cfg_attr(docsrs, doc(cfg(feature = "array")))] +unsafe impl ArrayItem for [I; N] {} + +/// Returns an array of bytes where all bytes of the value were +/// initialized using `getrandom_uninit`. Feature: `array`. +/// +/// Requires Rust 1.51. +/// +/// This can construct random byte arrays, and arbitrary levels of nested byte +/// arrays +/// +/// ``` +/// fn tls_hello_random() -> Result<[u8; 32], getrandom::Error> { +/// getrandom::array() +/// } +/// ``` +/// +/// The nested array support can be used to safely and efficiently construct +/// random values of types other than byte arrays: +/// ``` +/// # fn u32_array_example() -> Result<(), getrandom::Error> { +/// let random_u32s: [u32; 4] = +/// getrandom::array()?.map(u32::from_ne_bytes); +/// # Ok(()) +/// # } +/// ``` +/// +/// Multiple levels of array nesting can be used to construct more complicated +/// types, though some type annotations are needed: +/// ``` +/// # // TODO: Use `std::simd::Simd` when the `portable_simd` feature is +/// # // available; until then, here is a minimal polyfill for it that +/// # // allows the examle to work in stable Rust. +/// # struct Simd([T; N]); +/// # impl From<[T; N]> for Simd { +/// # fn from(value: [T; N]) -> Self { +/// # Self(value) +/// # } +/// # } +/// # fn simd_array_example() -> Result<(), getrandom::Error> { +/// let random_vectors: [Simd; 16] = +/// getrandom::array()? +/// .map(|bytes: [_; 4]| bytes.map(u32::from_ne_bytes)) +/// .map(Simd::from); +/// # Ok(()) +/// # } +/// ``` +/// +/// Arbitrary levels of nesting are supported. +/// ``` +/// # fn many_nesting_levels_example() -> Result<(), getrandom::Error> { +/// let many_nesting_levels: [[[[[[u8; 1]; 2]; 3]; 4]; 5]; 6] = getrandom::array()?; +/// # Ok(()) +/// # } +/// ``` +/// +/// The patterns above allows us to avoid implementing +/// `ArrayItem` for an endless number of types. +#[cfg_attr(docsrs, doc(cfg(feature = "array")))] +#[inline(always)] +pub fn array() -> Result<[I; N], Error> { + // This is `inline(always)` because the code generated by the compiler is + // terrible when it isn't inlined. + // + // This function only requires Rust 1.51 but `[T]::map()` used in the + // doctests is stable only in Rust 1.55. + + let mut uninit: MaybeUninit<[I; N]> = MaybeUninit::uninit(); + // TODO: `uninit.as_bytes_mut()` when that is available. + { + // SAFETY: MaybeUninit is always valid, even for padding bytes. + // The compiler will ensure that `B` isn't too large. + let as_bytes_mut = unsafe { + core::slice::from_raw_parts_mut( + uninit.as_mut_ptr() as *mut MaybeUninit, + core::mem::size_of::<[I; N]>(), + ) + }; + getrandom_uninit(as_bytes_mut)?; + } + // SAFETY: `dest` has been fully initialized by `getrandom_uninit` + // since it returned `Ok`. + Ok(unsafe { uninit.assume_init() }) +} diff --git a/src/lib.rs b/src/lib.rs index c4809d6b..947560df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,3 +327,9 @@ pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error // since it returned `Ok`. Ok(unsafe { slice_assume_init_mut(dest) }) } + +#[cfg(feature = "array")] +mod array; + +#[cfg(feature = "array")] +pub use array::*;