Repository: rust-random/getrandom (rust)

Issue:
Expose API interface for MaybeUninit<u8> slice
It is inefficient to zero-fill a large byte buffer before actually being initialized with random bytes. Thus, it would be better if getrandom exposes a function that takes a slice of `MaybeUninit<u8>` so that the user will not have to initialize the buffer.

Pull request description:
Add `getrandom_uninit_slice(dest: &mut [MaybeUninit<u8>]) -> ...`.
Add a public API for filling an `&mut [MaybeUninit<u8>]`. This will primarily serve as the building block for more typeful APIs for constructing random arrays.

Fixes #226.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8cf9a58..182236c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+### Added
+- `getrandom_uninit` [#291]
+
+### Breaking Changes
+- Update MSRV to 1.36 [#291]
+
+[#291]: https://github.com/rust-random/getrandom/pull/291
+
 ## [0.2.8] - 2022-10-20
 ### Changed
 - The [Web Cryptography API] will now be preferred on `wasm32-unknown-unknown`
diff --git a/README.md b/README.md
index df2307b..f43666a 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ crate features, WASM support and Custom RNGs see the
 
 ## Minimum Supported Rust Version
 
-This crate requires Rust 1.34.0 or later.
+This crate requires Rust 1.36.0 or later.
 
 # License
 
diff --git a/benches/mod.rs b/benches/mod.rs
index 11be47e..23b8c74 100644
--- a/benches/mod.rs
+++ b/benches/mod.rs
@@ -1,94 +1,64 @@
 #![feature(test)]
-extern crate test;
-
-use std::{
-    alloc::{alloc_zeroed, dealloc, Layout},
-    ptr::NonNull,
-};
-
-// AlignedBuffer is like a Box<[u8; N]> except that it is always N-byte aligned
-struct AlignedBuffer<const N: usize>(NonNull<[u8; N]>);
+#![feature(maybe_uninit_as_bytes)]
 
-impl<const N: usize> AlignedBuffer<N> {
-    fn layout() -> Layout {
-        Layout::from_size_align(N, N).unwrap()
-    }
-
-    fn new() -> Self {
-        let p = unsafe { alloc_zeroed(Self::layout()) } as *mut [u8; N];
-        Self(NonNull::new(p).unwrap())
-    }
-
-    fn buf(&mut self) -> &mut [u8; N] {
-        unsafe { self.0.as_mut() }
-    }
-}
+extern crate test;
 
-impl<const N: usize> Drop for AlignedBuffer<N> {
-    fn drop(&mut self) {
-        unsafe { dealloc(self.0.as_ptr() as *mut u8, Self::layout()) }
-    }
-}
+use std::mem::MaybeUninit;
 
 // Used to benchmark the throughput of getrandom in an optimal scenario.
 // The buffer is hot, and does not require initialization.
 #[inline(always)]
-fn bench<const N: usize>(b: &mut test::Bencher) {
-    let mut ab = AlignedBuffer::<N>::new();
-    let buf = ab.buf();
+fn bench_getrandom<const N: usize>(b: &mut test::Bencher) {
+    b.bytes = N as u64;
     b.iter(|| {
+        let mut buf = [0u8; N];
         getrandom::getrandom(&mut buf[..]).unwrap();
-        test::black_box(&buf);
+        test::black_box(buf);
     });
-    b.bytes = N as u64;
 }
 
 // Used to benchmark the throughput of getrandom is a slightly less optimal
 // scenario. The buffer is still hot, but requires initialization.
 #[inline(always)]
-fn bench_with_init<const N: usize>(b: &mut test::Bencher) {
-    let mut ab = AlignedBuffer::<N>::new();
-    let buf = ab.buf();
+fn bench_getrandom_uninit<const N: usize>(b: &mut test::Bencher) {
+    b.bytes = N as u64;
     b.iter(|| {
-        for byte in buf.iter_mut() {
-            *byte = 0;
-        }
-        getrandom::getrandom(&mut buf[..]).unwrap();
-        test::black_box(&buf);
+        let mut buf: MaybeUninit<[u8; N]> = MaybeUninit::uninit();
+        let _ = getrandom::getrandom_uninit(buf.as_bytes_mut()).unwrap();
+        let buf: [u8; N] = unsafe { buf.assume_init() };
+        test::black_box(buf)
     });
-    b.bytes = N as u64;
 }
 
-// 32 bytes (256-bit) is the seed sized used for rand::thread_rng
-const SEED: usize = 32;
-// Common size of a page, 4 KiB
-const PAGE: usize = 4096;
-// Large buffer to get asymptotic performance, 2 MiB
-const LARGE: usize = 1 << 21;
+macro_rules! bench {
+    ( $name:ident, $size:expr ) => {
+        pub mod $name {
+            #[bench]
+            pub fn bench_getrandom(b: &mut test::Bencher) {
+                super::bench_getrandom::<{ $size }>(b);
+            }
 
-#[bench]
-fn bench_seed(b: &mut test::Bencher) {
-    bench::<SEED>(b);
-}
-#[bench]
-fn bench_seed_init(b: &mut test::Bencher) {
-    bench_with_init::<SEED>(b);
+            #[bench]
+            pub fn bench_getrandom_uninit(b: &mut test::Bencher) {
+                super::bench_getrandom_uninit::<{ $size }>(b);
+            }
+        }
+    };
 }
 
-#[bench]
-fn bench_page(b: &mut test::Bencher) {
-    bench::<PAGE>(b);
-}
-#[bench]
-fn bench_page_init(b: &mut test::Bencher) {
-    bench_with_init::<PAGE>(b);
-}
+// 16 bytes (128 bits) is the size of an 128-bit AES key/nonce.
+bench!(aes128, 128 / 8);
 
-#[bench]
-fn bench_large(b: &mut test::Bencher) {
-    bench::<LARGE>(b);
-}
-#[bench]
-fn bench_large_init(b: &mut test::Bencher) {
-    bench_with_init::<LARGE>(b);
-}
+// 32 bytes (256 bits) is the seed sized used for rand::thread_rng
+// and the `random` value in a ClientHello/ServerHello for TLS.
+// This is also the size of a 256-bit AES/HMAC/P-256/Curve25519 key
+// and/or nonce.
+bench!(p256, 256 / 8);
+
+// A P-384/HMAC-384 key and/or nonce.
+bench!(p384, 384 / 8);
+
+// Initializing larger buffers is not the primary use case of this library, as
+// this should normally be done by a userspace CSPRNG. However, we have a test
+// here to see the effects of a lower (amortized) syscall overhead.
+bench!(page, 4096);
diff --git a/src/3ds.rs b/src/3ds.rs
index 6030512..87a32a1 100644
--- a/src/3ds.rs
+++ b/src/3ds.rs
@@ -9,8 +9,9 @@
 //! Implementation for Nintendo 3DS
 use crate::util_libc::sys_fill_exact;
 use crate::Error;
+use core::mem::MaybeUninit;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     sys_fill_exact(dest, |buf| unsafe {
         libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0)
     })
diff --git a/src/bsd_arandom.rs b/src/bsd_arandom.rs
index d441212..3539dd5 100644
--- a/src/bsd_arandom.rs
+++ b/src/bsd_arandom.rs
@@ -8,9 +8,9 @@
 
 //! Implementation for FreeBSD and NetBSD
 use crate::{util_libc::sys_fill_exact, Error};
-use core::ptr;
+use core::{mem::MaybeUninit, ptr};
 
-fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t {
+fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
     static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND];
     let mut len = buf.len();
     let ret = unsafe {
@@ -30,7 +30,7 @@ fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t {
     }
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // getrandom(2) was introduced in FreeBSD 12.0 and NetBSD 10.0
     #[cfg(target_os = "freebsd")]
     {
@@ -41,7 +41,9 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
 
         if let Some(fptr) = GETRANDOM.ptr() {
             let func: GetRandomFn = unsafe { core::mem::transmute(fptr) };
-            return sys_fill_exact(dest, |buf| unsafe { func(buf.as_mut_ptr(), buf.len(), 0) });
+            return sys_fill_exact(dest, |buf| unsafe {
+                func(buf.as_mut_ptr() as *mut u8, buf.len(), 0)
+            });
         }
     }
     // Both FreeBSD and NetBSD will only return up to 256 bytes at a time, and
diff --git a/src/custom.rs b/src/custom.rs
index 8432dfd..35771f1 100644
--- a/src/custom.rs
+++ b/src/custom.rs
@@ -7,8 +7,8 @@
 // except according to those terms.
 
 //! An implementation which calls out to an externally defined function.
-use crate::Error;
-use core::num::NonZeroU32;
+use crate::{util::uninit_slice_fill_zero, Error};
+use core::{mem::MaybeUninit, num::NonZeroU32};
 
 /// Register a function to be invoked by `getrandom` on unsupported targets.
 ///
@@ -90,10 +90,16 @@ macro_rules! register_custom_getrandom {
 }
 
 #[allow(dead_code)]
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     extern "C" {
         fn __getrandom_custom(dest: *mut u8, len: usize) -> u32;
     }
+    // Previously we always passed a valid, initialized slice to
+    // `__getrandom_custom`. Ensure `dest` has been initialized for backward
+    // compatibility with implementations that rely on that (e.g. Rust
+    // implementations that construct a `&mut [u8]` slice from `dest` and
+    // `len`).
+    let dest = uninit_slice_fill_zero(dest);
     let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) };
     match NonZeroU32::new(ret) {
         None => Ok(()),
diff --git a/src/dragonfly.rs b/src/dragonfly.rs
index 8daaa40..c3701cc 100644
--- a/src/dragonfly.rs
+++ b/src/dragonfly.rs
@@ -12,8 +12,9 @@ use crate::{
     util_libc::{sys_fill_exact, Weak},
     Error,
 };
+use std::mem::MaybeUninit;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") };
     type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t;
 
diff --git a/src/espidf.rs b/src/espidf.rs
index dce8a2a..d074dc4 100644
--- a/src/espidf.rs
+++ b/src/espidf.rs
@@ -8,13 +8,13 @@
 
 //! Implementation for ESP-IDF
 use crate::Error;
-use core::ffi::c_void;
+use core::{ffi::c_void, mem::MaybeUninit};
 
 extern "C" {
     fn esp_fill_random(buf: *mut c_void, len: usize) -> u32;
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`)
     // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process:
     // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html
diff --git a/src/fuchsia.rs b/src/fuchsia.rs
index 572ff53..5a135f3 100644
--- a/src/fuchsia.rs
+++ b/src/fuchsia.rs
@@ -8,13 +8,14 @@
 
 //! Implementation for Fuchsia Zircon
 use crate::Error;
+use core::mem::MaybeUninit;
 
 #[link(name = "zircon")]
 extern "C" {
     fn zx_cprng_draw(buffer: *mut u8, length: usize);
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
-    unsafe { zx_cprng_draw(dest.as_mut_ptr(), dest.len()) }
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
+    unsafe { zx_cprng_draw(dest.as_mut_ptr() as *mut u8, dest.len()) }
     Ok(())
 }
diff --git a/src/ios.rs b/src/ios.rs
index 226de16..8f90485 100644
--- a/src/ios.rs
+++ b/src/ios.rs
@@ -8,16 +8,16 @@
 
 //! Implementation for iOS
 use crate::Error;
-use core::{ffi::c_void, ptr::null};
+use core::{ffi::c_void, mem::MaybeUninit, ptr::null};
 
 #[link(name = "Security", kind = "framework")]
 extern "C" {
     fn SecRandomCopyBytes(rnd: *const c_void, count: usize, bytes: *mut u8) -> i32;
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // Apple's documentation guarantees kSecRandomDefault is a synonym for NULL.
-    let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr()) };
+    let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr() as *mut u8) };
     // errSecSuccess (from SecBase.h) is always zero.
     if ret != 0 {
         Err(Error::IOS_SEC_RANDOM)
diff --git a/src/js.rs b/src/js.rs
index 574c4dc..7b9ae31 100644
--- a/src/js.rs
+++ b/src/js.rs
@@ -5,10 +5,10 @@
 // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
-use crate::Error;
+use crate::{util::uninit_slice_fill_zero, Error};
 
 extern crate std;
-use std::thread_local;
+use std::{mem::MaybeUninit, thread_local};
 
 use js_sys::{global, Function, Uint8Array};
 use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
@@ -28,12 +28,16 @@ thread_local!(
     static RNG_SOURCE: Result<RngSource, Error> = getrandom_init();
 );
 
-pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub(crate) fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     RNG_SOURCE.with(|result| {
         let source = result.as_ref().map_err(|&e| e)?;
 
         match source {
             RngSource::Node(n) => {
+                // XXX(perf): `random_fill_sync` requires a `&mut [u8]` so we
+                // have to ensure the memory in `dest` is initialized.
+                let dest = uninit_slice_fill_zero(dest);
+
                 if n.random_fill_sync(dest).is_err() {
                     return Err(Error::NODE_RANDOM_FILL_SYNC);
                 }
@@ -49,7 +53,9 @@ pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
                     if crypto.get_random_values(&sub_buf).is_err() {
                         return Err(Error::WEB_GET_RANDOM_VALUES);
                     }
-                    sub_buf.copy_to(chunk);
+
+                    // SAFETY: `sub_buf`'s length is the same length as `chunk`
+                    unsafe { sub_buf.raw_copy_to_ptr(chunk.as_mut_ptr() as *mut u8) };
                 }
             }
         };
diff --git a/src/lib.rs b/src/lib.rs
index 67325a3..c4809d6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -186,6 +186,9 @@
 #[macro_use]
 extern crate cfg_if;
 
+use crate::util::{slice_as_uninit_mut, slice_assume_init_mut};
+use core::mem::MaybeUninit;
+
 mod error;
 mod util;
 // To prevent a breaking change when targets are added, we always export the
@@ -199,7 +202,11 @@ pub use crate::error::Error;
 
 // System-specific implementations.
 //
-// These should all provide getrandom_inner with the same signature as getrandom.
+// These should all provide getrandom_inner with the signature
+// `fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>`.
+// The function MUST fully initialize `dest` when `Ok(())` is returned.
+// The function MUST NOT ever write uninitialized bytes into `dest`,
+// regardless of what value it returns.
 cfg_if! {
     if #[cfg(any(target_os = "emscripten", target_os = "haiku",
                  target_os = "redox"))] {
@@ -283,9 +290,40 @@ cfg_if! {
 /// In general, `getrandom` will be fast enough for interactive usage, though
 /// significantly slower than a user-space CSPRNG; for the latter consider
 /// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
+#[inline]
 pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
-    if dest.is_empty() {
-        return Ok(());
-    }
-    imp::getrandom_inner(dest)
+    // SAFETY: The `&mut MaybeUninit<_>` reference doesn't escape, and
+    // `getrandom_uninit` guarantees it will never de-initialize any part of
+    // `dest`.
+    getrandom_uninit(unsafe { slice_as_uninit_mut(dest) })?;
+    Ok(())
+}
+
+/// Version of the `getrandom` function which fills `dest` with random bytes
+/// returns a mutable reference to those bytes.
+///
+/// On successful completion this function is guaranteed to return a slice
+/// which points to the same memory as `dest` and has the same length.
+/// In other words, it's safe to assume that `dest` is initialized after
+/// this function has returned `Ok`.
+///
+/// No part of `dest` will ever be de-initialized at any point, regardless
+/// of what is returned.
+///
+/// # Examples
+///
+/// ```ignore
+/// # // We ignore this test since `uninit_array` is unstable.
+/// #![feature(maybe_uninit_uninit_array)]
+/// # fn main() -> Result<(), getrandom::Error> {
+/// let mut buf = core::mem::MaybeUninit::uninit_array::<1024>();
+/// let buf: &mut [u8] = getrandom::getrandom_uninit(&mut buf)?;
+/// # Ok(()) }
+/// ```
+#[inline]
+pub fn getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
+    imp::getrandom_inner(dest)?;
+    // SAFETY: `dest` has been fully initialized by `imp::getrandom_inner`
+    // since it returned `Ok`.
+    Ok(unsafe { slice_assume_init_mut(dest) })
 }
diff --git a/src/linux_android.rs b/src/linux_android.rs
index 4270b67..e81f1e1 100644
--- a/src/linux_android.rs
+++ b/src/linux_android.rs
@@ -12,8 +12,9 @@ use crate::{
     util_libc::{last_os_error, sys_fill_exact},
     {use_file, Error},
 };
+use core::mem::MaybeUninit;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // getrandom(2) was introduced in Linux 3.17
     static HAS_GETRANDOM: LazyBool = LazyBool::new();
     if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
diff --git a/src/macos.rs b/src/macos.rs
index 671a053..312f9b2 100644
--- a/src/macos.rs
+++ b/src/macos.rs
@@ -12,17 +12,17 @@ use crate::{
     util_libc::{last_os_error, Weak},
     Error,
 };
-use core::mem;
+use core::mem::{self, MaybeUninit};
 
 type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // getentropy(2) was added in 10.12, Rust supports 10.7+
     static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") };
     if let Some(fptr) = GETENTROPY.ptr() {
         let func: GetEntropyFn = unsafe { mem::transmute(fptr) };
         for chunk in dest.chunks_mut(256) {
-            let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) };
+            let ret = unsafe { func(chunk.as_mut_ptr() as *mut u8, chunk.len()) };
             if ret != 0 {
                 return Err(last_os_error());
             }
diff --git a/src/openbsd.rs b/src/openbsd.rs
index 4137173..5d4df50 100644
--- a/src/openbsd.rs
+++ b/src/openbsd.rs
@@ -9,7 +9,7 @@
 //! Implementation for OpenBSD
 use crate::{util_libc::last_os_error, Error};
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // getentropy(2) was added in OpenBSD 5.6, so we can use it unconditionally.
     for chunk in dest.chunks_mut(256) {
         let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) };
diff --git a/src/rdrand.rs b/src/rdrand.rs
index 1df21e5..910b535 100644
--- a/src/rdrand.rs
+++ b/src/rdrand.rs
@@ -7,8 +7,8 @@
 // except according to those terms.
 
 //! Implementation for SGX using RDRAND instruction
-use crate::Error;
-use core::mem;
+use crate::{util::slice_as_uninit, Error};
+use core::mem::{self, MaybeUninit};
 
 cfg_if! {
     if #[cfg(target_arch = "x86_64")] {
@@ -69,7 +69,7 @@ fn is_rdrand_supported() -> bool {
     HAS_RDRAND.unsync_init(|| unsafe { (arch::__cpuid(1).ecx & FLAG) != 0 })
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     if !is_rdrand_supported() {
         return Err(Error::NO_RDRAND);
     }
@@ -80,18 +80,18 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
 }
 
 #[target_feature(enable = "rdrand")]
-unsafe fn rdrand_exact(dest: &mut [u8]) -> Result<(), Error> {
+unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // We use chunks_exact_mut instead of chunks_mut as it allows almost all
     // calls to memcpy to be elided by the compiler.
     let mut chunks = dest.chunks_exact_mut(WORD_SIZE);
     for chunk in chunks.by_ref() {
-        chunk.copy_from_slice(&rdrand()?);
+        chunk.copy_from_slice(slice_as_uninit(&rdrand()?));
     }
 
     let tail = chunks.into_remainder();
     let n = tail.len();
     if n > 0 {
-        tail.copy_from_slice(&rdrand()?[..n]);
+        tail.copy_from_slice(slice_as_uninit(&rdrand()?[..n]));
     }
     Ok(())
 }
diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs
index cf3067d..eaf2709 100644
--- a/src/solaris_illumos.rs
+++ b/src/solaris_illumos.rs
@@ -29,7 +29,7 @@ type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) ->
 #[cfg(target_os = "solaris")]
 type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // getrandom(2) was introduced in Solaris 11.3 for Illumos in 2015.
     static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") };
     if let Some(fptr) = GETRANDOM.ptr() {
diff --git a/src/solid.rs b/src/solid.rs
index dc76aac..aeccc4e 100644
--- a/src/solid.rs
+++ b/src/solid.rs
@@ -8,14 +8,14 @@
 
 //! Implementation for SOLID
 use crate::Error;
-use core::num::NonZeroU32;
+use core::{mem::MaybeUninit, num::NonZeroU32};
 
 extern "C" {
     pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32;
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
-    let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr(), dest.len()) };
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
+    let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr() as *mut u8, dest.len()) };
     if ret >= 0 {
         Ok(())
     } else {
diff --git a/src/use_file.rs b/src/use_file.rs
index 16c0216..fb45827 100644
--- a/src/use_file.rs
+++ b/src/use_file.rs
@@ -14,6 +14,7 @@ use crate::{
 };
 use core::{
     cell::UnsafeCell,
+    mem::MaybeUninit,
     sync::atomic::{AtomicUsize, Ordering::Relaxed},
 };
 
@@ -29,9 +30,11 @@ const FILE_PATH: &str = "/dev/random\0";
 #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
 const FILE_PATH: &str = "/dev/urandom\0";
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     let fd = get_rng_fd()?;
-    let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
+    let read = |buf: &mut [MaybeUninit<u8>]| unsafe {
+        libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len())
+    };
 
     if cfg!(target_os = "emscripten") {
         // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes.
diff --git a/src/util.rs b/src/util.rs
index 06e23c2..3162afa 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -6,7 +6,11 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 #![allow(dead_code)]
-use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
+use core::{
+    mem::MaybeUninit,
+    ptr,
+    sync::atomic::{AtomicUsize, Ordering::Relaxed},
+};
 
 // This structure represents a lazily initialized static usize value. Useful
 // when it is preferable to just rerun initialization instead of locking.
@@ -62,3 +66,36 @@ impl LazyBool {
         self.0.unsync_init(|| init() as usize) != 0
     }
 }
+
+/// Polyfill for `maybe_uninit_slice` feature's
+/// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have
+/// been initialized.
+#[inline(always)]
+pub unsafe fn slice_assume_init_mut<T>(slice: &mut [MaybeUninit<T>]) -> &mut [T] {
+    // SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`.
+    &mut *(slice as *mut [MaybeUninit<T>] as *mut [T])
+}
+
+#[inline]
+pub fn uninit_slice_fill_zero(slice: &mut [MaybeUninit<u8>]) -> &mut [u8] {
+    unsafe { ptr::write_bytes(slice.as_mut_ptr(), 0, slice.len()) };
+    unsafe { slice_assume_init_mut(slice) }
+}
+
+#[inline(always)]
+pub fn slice_as_uninit<T>(slice: &[T]) -> &[MaybeUninit<T>] {
+    // SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`.
+    // There is no risk of writing a `MaybeUninit<T>` into the result since
+    // the result isn't mutable.
+    unsafe { &*(slice as *const [T] as *const [MaybeUninit<T>]) }
+}
+
+/// View an mutable initialized array as potentially-uninitialized.
+///
+/// This is unsafe because it allows assigning uninitialized values into
+/// `slice`, which would be undefined behavior.
+#[inline(always)]
+pub unsafe fn slice_as_uninit_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
+    // SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`.
+    &mut *(slice as *mut [T] as *mut [MaybeUninit<T>])
+}
diff --git a/src/util_libc.rs b/src/util_libc.rs
index d057071..6c80b3f 100644
--- a/src/util_libc.rs
+++ b/src/util_libc.rs
@@ -8,6 +8,7 @@
 #![allow(dead_code)]
 use crate::Error;
 use core::{
+    mem::MaybeUninit,
     num::NonZeroU32,
     ptr::NonNull,
     sync::atomic::{fence, AtomicPtr, Ordering},
@@ -59,8 +60,8 @@ pub fn last_os_error() -> Error {
 //   - should return -1 and set errno on failure
 //   - should return the number of bytes written on success
 pub fn sys_fill_exact(
-    mut buf: &mut [u8],
-    sys_fill: impl Fn(&mut [u8]) -> libc::ssize_t,
+    mut buf: &mut [MaybeUninit<u8>],
+    sys_fill: impl Fn(&mut [MaybeUninit<u8>]) -> libc::ssize_t,
 ) -> Result<(), Error> {
     while !buf.is_empty() {
         let res = sys_fill(buf);
diff --git a/src/vxworks.rs b/src/vxworks.rs
index 6cb5d52..9b2090f 100644
--- a/src/vxworks.rs
+++ b/src/vxworks.rs
@@ -8,9 +8,12 @@
 
 //! Implementation for VxWorks
 use crate::{util_libc::last_os_error, Error};
-use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
+use core::{
+    mem::MaybeUninit,
+    sync::atomic::{AtomicBool, Ordering::Relaxed},
+};
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     static RNG_INIT: AtomicBool = AtomicBool::new(false);
     while !RNG_INIT.load(Relaxed) {
         let ret = unsafe { libc::randSecure() };
@@ -25,7 +28,7 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
 
     // Prevent overflow of i32
     for chunk in dest.chunks_mut(i32::max_value() as usize) {
-        let ret = unsafe { libc::randABytes(chunk.as_mut_ptr(), chunk.len() as i32) };
+        let ret = unsafe { libc::randABytes(chunk.as_mut_ptr() as *mut u8, chunk.len() as i32) };
         if ret != 0 {
             return Err(last_os_error());
         }
diff --git a/src/wasi.rs b/src/wasi.rs
index c512182..f7a54f8 100644
--- a/src/wasi.rs
+++ b/src/wasi.rs
@@ -8,10 +8,10 @@
 
 //! Implementation for WASI
 use crate::Error;
-use core::num::NonZeroU32;
+use core::{mem::MaybeUninit, num::NonZeroU32};
 use wasi::wasi_snapshot_preview1::random_get;
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     match unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) } {
         0 => Ok(()),
         err => Err(unsafe { NonZeroU32::new_unchecked(err as u32) }.into()),
diff --git a/src/windows.rs b/src/windows.rs
index 41dc37a..e5a626c 100644
--- a/src/windows.rs
+++ b/src/windows.rs
@@ -7,7 +7,7 @@
 // except according to those terms.
 
 use crate::Error;
-use core::{ffi::c_void, num::NonZeroU32, ptr};
+use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
 
 const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
 
@@ -21,14 +21,14 @@ extern "system" {
     ) -> u32;
 }
 
-pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
+pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     // Prevent overflow of u32
     for chunk in dest.chunks_mut(u32::max_value() as usize) {
         // BCryptGenRandom was introduced in Windows Vista
         let ret = unsafe {
             BCryptGenRandom(
                 ptr::null_mut(),
-                chunk.as_mut_ptr(),
+                chunk.as_mut_ptr() as *mut u8,
                 chunk.len() as u32,
                 BCRYPT_USE_SYSTEM_PREFERRED_RNG,
             )


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8ca2d01..210d67b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -44,7 +44,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, windows-latest]
-        toolchain: [nightly, beta, stable, 1.34]
+        toolchain: [nightly, beta, stable, 1.36]
         # Only Test macOS on stable to reduce macOS CI jobs
         include:
           - os: macos-latest
diff --git a/tests/rdrand.rs b/tests/rdrand.rs
index 4ff85c4..2567868 100644
--- a/tests/rdrand.rs
+++ b/tests/rdrand.rs
@@ -11,5 +11,10 @@ mod rdrand;
 #[path = "../src/util.rs"]
 mod util;
 
-use rdrand::getrandom_inner as getrandom_impl;
+// The rdrand implementation has the signature of getrandom_uninit(), but our
+// tests expect getrandom_impl() to have the signature of getrandom().
+fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> {
+    rdrand::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?;
+    Ok(())
+}
 mod common;