Repository: rust-random/getrandom
Pull request #544 (2024-11-25 13:37:15)

Add functions to generate random `u32` and `u64` values
These functions can be helpful for seed generation and implementation of `OsRng`. Additionally, some backends (Hermit, RDRAND, RNDR, WASI p2) can directly generate random `u32`/`u64` values. Relying on the byte API may be less efficient in these cases.

Using `u32` and `u64` as function names may seem problematic, but based on the `fasrand` experience, it works well in practice, provided that users reference them as `getrandom::u32/u64` without importing them directly.

newpavlov: cc @dhardy @briansmith 
dhardy: @newpavlov maybe though not really important for `rand_core` (we can already convert from bits). In any case, I won't depend on this until a new release is out so don't expect it to see use in rand v0.9.
newpavlov: The main motivation is user convenience, since the main use case of this crate is seed generation. The `u32`/`u64` generation support by some platforms is just an additional argument for why it should be done in this crate.
dhardy: > The main motivation is user convenience, since the main use case of this crate is seed generation.

I always saw this crate as a low level implementation crate, not something which would be used *directly* by many users.

getrandom does now have 1093 dependents to rand's 16269, so maybe I'm partly wrong.

Patch:
diff --git a/src/backends.rs b/src/backends.rs
index 7ed94cf..d516417 100644
--- a/src/backends.rs
+++ b/src/backends.rs
@@ -116,8 +116,20 @@ cfg_if! {
         mod apple_other;
         pub use apple_other::*;
     } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] {
-        mod wasi;
-        pub use wasi::*;
+        cfg_if! {
+            if #[cfg(target_env = "p1")] {
+                mod wasi_p1;
+                pub use wasi_p1::*;
+            } else if #[cfg(target_env = "p2")] {
+                mod wasi_p2;
+                pub use wasi_p2::*;
+            } else {
+                compile_error!(
+                    "Unknown version of WASI (only previews 1 and 2 are supported) \
+                    or Rust version older than 1.80 was used"
+                );
+            }
+        }
     } else if #[cfg(target_os = "hermit")] {
         mod hermit;
         pub use hermit::*;
diff --git a/src/backends/apple_other.rs b/src/backends/apple_other.rs
index 1990523..127a31e 100644
--- a/src/backends/apple_other.rs
+++ b/src/backends/apple_other.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     let dst_ptr = dest.as_mut_ptr().cast::<c_void>();
     let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) };
diff --git a/src/backends/custom.rs b/src/backends/custom.rs
index e97d329..0c48294 100644
--- a/src/backends/custom.rs
+++ b/src/backends/custom.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::mem::MaybeUninit;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     extern "Rust" {
         fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>;
diff --git a/src/backends/esp_idf.rs b/src/backends/esp_idf.rs
index 2df7b9a..0f0ff7f 100644
--- a/src/backends/esp_idf.rs
+++ b/src/backends/esp_idf.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[cfg(not(target_os = "espidf"))]
 compile_error!("`esp_idf` backend can be enabled only for ESP-IDF targets!");
 
diff --git a/src/backends/fuchsia.rs b/src/backends/fuchsia.rs
index b8e6de8..5edd210 100644
--- a/src/backends/fuchsia.rs
+++ b/src/backends/fuchsia.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::mem::MaybeUninit;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[link(name = "zircon")]
 extern "C" {
     fn zx_cprng_draw(buffer: *mut u8, length: usize);
diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs
index c93c6cc..e0b2d34 100644
--- a/src/backends/getentropy.rs
+++ b/src/backends/getentropy.rs
@@ -10,6 +10,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 mod util_libc;
 
diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs
index 298059c..a00829f 100644
--- a/src/backends/getrandom.rs
+++ b/src/backends/getrandom.rs
@@ -18,6 +18,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 mod util_libc;
 
diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs
index 594b330..11bfbf2 100644
--- a/src/backends/hermit.rs
+++ b/src/backends/hermit.rs
@@ -4,6 +4,32 @@ use core::mem::MaybeUninit;
 
 extern "C" {
     fn sys_read_entropy(buffer: *mut u8, length: usize, flags: u32) -> isize;
+    // Note that `sys_secure_rand32/64` are implemented using `sys_read_entropy`:
+    // https://github.com/hermit-os/kernel/blob/430da84/src/syscalls/entropy.rs#L62-L104
+    // But this may change in future and can depend on compilation target,
+    // so to future-proof we use these "syscalls".
+    fn sys_secure_rand32(value: *mut u32) -> i32;
+    fn sys_secure_rand64(value: *mut u64) -> i32;
+}
+
+pub fn inner_u32() -> Result<u32, Error> {
+    let mut res = MaybeUninit::uninit();
+    let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) };
+    match ret {
+        0 => Ok(unsafe { res.assume_init() }),
+        -1 => Err(Error::UNSUPPORTED),
+        _ => Err(Error::UNEXPECTED),
+    }
+}
+
+pub fn inner_u64() -> Result<u64, Error> {
+    let mut res = MaybeUninit::uninit();
+    let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) };
+    match ret {
+        0 => Ok(unsafe { res.assume_init() }),
+        -1 => Err(Error::UNSUPPORTED),
+        _ => Err(Error::UNEXPECTED),
+    }
 }
 
 pub fn fill_inner(mut dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
diff --git a/src/backends/linux_android.rs b/src/backends/linux_android.rs
index cc59b0b..6c0b66a 100644
--- a/src/backends/linux_android.rs
+++ b/src/backends/linux_android.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::mem::MaybeUninit;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 mod util_libc;
 
diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs
index 4e3ea98..cdfff98 100644
--- a/src/backends/linux_android_with_fallback.rs
+++ b/src/backends/linux_android_with_fallback.rs
@@ -9,6 +9,8 @@ use core::{
 };
 use use_file::util_libc;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
 
 /// Sentinel value which indicates that `libc::getrandom` either not available,
diff --git a/src/backends/linux_rustix.rs b/src/backends/linux_rustix.rs
index 4fef338..d3bcce3 100644
--- a/src/backends/linux_rustix.rs
+++ b/src/backends/linux_rustix.rs
@@ -2,6 +2,8 @@
 use crate::{Error, MaybeUninit};
 use rustix::rand::{getrandom_uninit, GetRandomFlags};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[cfg(not(any(target_os = "android", target_os = "linux")))]
 compile_error!("`linux_rustix` backend can be enabled only for Linux/Android targets!");
 
diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs
index ddb6973..0e3268e 100644
--- a/src/backends/netbsd.rs
+++ b/src/backends/netbsd.rs
@@ -12,6 +12,8 @@ use core::{
     sync::atomic::{AtomicPtr, Ordering},
 };
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 mod util_libc;
 
diff --git a/src/backends/rdrand.rs b/src/backends/rdrand.rs
index 0a278b9..19fa97b 100644
--- a/src/backends/rdrand.rs
+++ b/src/backends/rdrand.rs
@@ -20,6 +20,8 @@ cfg_if! {
     }
 }
 
+static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new();
+
 // Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
 // Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
 // Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
@@ -99,15 +101,6 @@ fn is_rdrand_good() -> bool {
     unsafe { self_test() }
 }
 
-pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
-    static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new();
-    if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
-        return Err(Error::NO_RDRAND);
-    }
-    // SAFETY: After this point, we know rdrand is supported.
-    unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND)
-}
-
 // TODO: make this function safe when we have feature(target_feature_11)
 #[target_feature(enable = "rdrand")]
 unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
@@ -127,3 +120,53 @@ unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
     }
     Some(())
 }
+
+#[cfg(target_arch = "x86_64")]
+#[target_feature(enable = "rdrand")]
+unsafe fn rdrand_u32() -> Option<u32> {
+    rdrand().map(crate::util::truncate)
+}
+
+#[cfg(target_arch = "x86_64")]
+#[target_feature(enable = "rdrand")]
+unsafe fn rdrand_u64() -> Option<u64> {
+    rdrand()
+}
+
+#[cfg(target_arch = "x86")]
+#[target_feature(enable = "rdrand")]
+unsafe fn rdrand_u32() -> Option<u32> {
+    rdrand()
+}
+
+#[cfg(target_arch = "x86")]
+#[target_feature(enable = "rdrand")]
+unsafe fn rdrand_u64() -> Option<u64> {
+    let a = rdrand()?;
+    let b = rdrand()?;
+    Some((u64::from(a) << 32) || u64::from(b))
+}
+
+pub fn inner_u32() -> Result<u32, Error> {
+    if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
+        return Err(Error::NO_RDRAND);
+    }
+    // SAFETY: After this point, we know rdrand is supported.
+    unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND)
+}
+
+pub fn inner_u64() -> Result<u64, Error> {
+    if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
+        return Err(Error::NO_RDRAND);
+    }
+    // SAFETY: After this point, we know rdrand is supported.
+    unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND)
+}
+
+pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
+    if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
+        return Err(Error::NO_RDRAND);
+    }
+    // SAFETY: After this point, we know rdrand is supported.
+    unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND)
+}
diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs
index b6805b2..bd2878f 100644
--- a/src/backends/rndr.rs
+++ b/src/backends/rndr.rs
@@ -2,7 +2,10 @@
 //!
 //! Arm Architecture Reference Manual for A-profile architecture:
 //! ARM DDI 0487K.a, ID032224, D23.2.147 RNDR, Random Number
-use crate::{util::slice_as_uninit, Error};
+use crate::{
+    util::{slice_as_uninit, truncate},
+    Error,
+};
 use core::arch::asm;
 use core::mem::{size_of, MaybeUninit};
 
@@ -101,6 +104,26 @@ fn is_rndr_available() -> bool {
     }
 }
 
+pub fn inner_u32() -> Result<u32, Error> {
+    if is_rndr_available() {
+        // SAFETY: after this point, we know the `rand` target feature is enabled
+        let res = unsafe { rndr() };
+        res.map(truncate).ok_or(Error::RNDR_FAILURE)
+    } else {
+        Err(Error::RNDR_NOT_AVAILABLE)
+    }
+}
+
+pub fn inner_u64() -> Result<u64, Error> {
+    if is_rndr_available() {
+        // SAFETY: after this point, we know the `rand` target feature is enabled
+        let res = unsafe { rndr() };
+        res.ok_or(Error::RNDR_FAILURE)
+    } else {
+        Err(Error::RNDR_NOT_AVAILABLE)
+    }
+}
+
 pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     if is_rndr_available() {
         // SAFETY: after this point, we know the `rand` target feature is enabled
diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs
index a9a2680..ea5344f 100644
--- a/src/backends/solaris.rs
+++ b/src/backends/solaris.rs
@@ -15,6 +15,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 mod util_libc;
 
diff --git a/src/backends/solid.rs b/src/backends/solid.rs
index 50bbe47..6699e68 100644
--- a/src/backends/solid.rs
+++ b/src/backends/solid.rs
@@ -2,6 +2,8 @@
 use crate::Error;
 use core::mem::MaybeUninit;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 extern "C" {
     pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32;
 }
diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs
index 86dfb48..ef12fca 100644
--- a/src/backends/use_file.rs
+++ b/src/backends/use_file.rs
@@ -6,6 +6,9 @@ use core::{
     sync::atomic::{AtomicI32, Ordering},
 };
 
+#[cfg(not(any(target_os = "android", target_os = "linux")))]
+pub use crate::util::{inner_u32, inner_u64};
+
 #[path = "../util_libc.rs"]
 pub(super) mod util_libc;
 
diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs
index d595d17..51b7580 100644
--- a/src/backends/vxworks.rs
+++ b/src/backends/vxworks.rs
@@ -9,6 +9,8 @@ use core::{
 #[path = "../util_libc.rs"]
 mod util_libc;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
     static RNG_INIT: AtomicBool = AtomicBool::new(false);
     while !RNG_INIT.load(Relaxed) {
diff --git a/src/backends/wasi.rs b/src/backends/wasi.rs
deleted file mode 100644
index a1ecf00..0000000
--- a/src/backends/wasi.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-//! Implementation for WASI (preview 1 and 2)
-//!
-//! `target_env = "p1"` was introduced only in Rust 1.80, so on earlier compiler versions this
-//! code will result in a compilation error.
-use crate::Error;
-use core::mem::MaybeUninit;
-
-#[cfg(not(any(target_env = "p1", target_env = "p2")))]
-compile_error!(
-    "Unknown version of WASI (only previews 1 and 2 are supported) \
-    or Rust version older than 1.80 was used"
-);
-
-#[cfg(target_env = "p1")]
-pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
-    // This linking is vendored from the wasi crate:
-    // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350
-    #[link(wasm_import_module = "wasi_snapshot_preview1")]
-    extern "C" {
-        fn random_get(arg0: i32, arg1: i32) -> i32;
-    }
-
-    // Based on the wasi code:
-    // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062
-    // Note that size of an allocated object can not be bigger than isize::MAX bytes.
-    // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe.
-    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
-    let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) };
-    match ret {
-        0 => Ok(()),
-        code => {
-            let err = u32::try_from(code)
-                .map(Error::from_os_error)
-                .unwrap_or(Error::UNEXPECTED);
-            Err(err)
-        }
-    }
-}
-
-#[cfg(target_env = "p2")]
-pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
-    use core::ptr::copy_nonoverlapping;
-    use wasi::random::random::get_random_u64;
-
-    let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::<MaybeUninit<u64>>() };
-
-    // We use `get_random_u64` instead of `get_random_bytes` because the latter creates
-    // an allocation due to the Wit IDL [restrictions][0]. This should be fine since
-    // the main use case of `getrandom` is seed generation.
-    //
-    // [0]: https://github.com/WebAssembly/wasi-random/issues/27
-    if !prefix.is_empty() {
-        let val = get_random_u64();
-        let src = (&val as *const u64).cast();
-        unsafe {
-            copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len());
-        }
-    }
-
-    for dst in chunks {
-        dst.write(get_random_u64());
-    }
-
-    if !suffix.is_empty() {
-        let val = get_random_u64();
-        let src = (&val as *const u64).cast();
-        unsafe {
-            copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len());
-        }
-    }
-
-    Ok(())
-}
diff --git a/src/backends/wasi_p1.rs b/src/backends/wasi_p1.rs
new file mode 100644
index 0000000..6eefdee
--- /dev/null
+++ b/src/backends/wasi_p1.rs
@@ -0,0 +1,30 @@
+//! Implementation for WASI Preview 1
+use crate::Error;
+use core::mem::MaybeUninit;
+
+pub use crate::util::{inner_u32, inner_u64};
+
+// This linking is vendored from the wasi crate:
+// https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350
+#[link(wasm_import_module = "wasi_snapshot_preview1")]
+extern "C" {
+    fn random_get(arg0: i32, arg1: i32) -> i32;
+}
+
+pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
+    // Based on the wasi code:
+    // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062
+    // Note that size of an allocated object can not be bigger than isize::MAX bytes.
+    // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe.
+    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
+    let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) };
+    match ret {
+        0 => Ok(()),
+        code => {
+            let err = u32::try_from(code)
+                .map(Error::from_os_error)
+                .unwrap_or(Error::UNEXPECTED);
+            Err(err)
+        }
+    }
+}
diff --git a/src/backends/wasi_p2.rs b/src/backends/wasi_p2.rs
new file mode 100644
index 0000000..9d5d601
--- /dev/null
+++ b/src/backends/wasi_p2.rs
@@ -0,0 +1,47 @@
+//! Implementation for WASI Preview 2.
+use crate::Error;
+use core::mem::MaybeUninit;
+use wasi::random::random::get_random_u64;
+
+pub fn inner_u32() -> Result<u32, Error> {
+    let val = get_random_u64();
+    Ok(crate::util::truncate(val))
+}
+
+pub fn inner_u64() -> Result<u64, Error> {
+    Ok(get_random_u64())
+}
+
+pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
+    use core::ptr::copy_nonoverlapping;
+    use wasi::random::random::get_random_u64;
+
+    let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::<MaybeUninit<u64>>() };
+
+    // We use `get_random_u64` instead of `get_random_bytes` because the latter creates
+    // an allocation due to the Wit IDL [restrictions][0]. This should be fine since
+    // the main use case of `getrandom` is seed generation.
+    //
+    // [0]: https://github.com/WebAssembly/wasi-random/issues/27
+    if !prefix.is_empty() {
+        let val = get_random_u64();
+        let src = (&val as *const u64).cast();
+        unsafe {
+            copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len());
+        }
+    }
+
+    for dst in chunks {
+        dst.write(get_random_u64());
+    }
+
+    if !suffix.is_empty() {
+        let val = get_random_u64();
+        let src = (&val as *const u64).cast();
+        unsafe {
+            copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len());
+        }
+    }
+
+    Ok(())
+}
diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs
index 5cdca57..de2b433 100644
--- a/src/backends/wasm_js.rs
+++ b/src/backends/wasm_js.rs
@@ -4,6 +4,8 @@ use crate::Error;
 extern crate std;
 use std::{mem::MaybeUninit, thread_local};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 #[cfg(not(all(
     any(target_arch = "wasm32", target_arch = "wasm64"),
     target_os = "unknown",
diff --git a/src/backends/windows.rs b/src/backends/windows.rs
index 642206e..6c8e46b 100644
--- a/src/backends/windows.rs
+++ b/src/backends/windows.rs
@@ -23,6 +23,8 @@
 use crate::Error;
 use core::mem::MaybeUninit;
 
+pub use crate::util::{inner_u32, inner_u64};
+
 // Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As
 // bcryptprimitives.dll lacks an import library, we use the windows-targets
 // crate to link to it.
diff --git a/src/backends/windows7.rs b/src/backends/windows7.rs
index 573ac88..2546719 100644
--- a/src/backends/windows7.rs
+++ b/src/backends/windows7.rs
@@ -12,6 +12,8 @@
 use crate::Error;
 use core::{ffi::c_void, mem::MaybeUninit};
 
+pub use crate::util::{inner_u32, inner_u64};
+
 // Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom
 // API. Don't use windows-targets as it doesn't support Windows 7 targets.
 #[link(name = "advapi32")]
diff --git a/src/lib.rs b/src/lib.rs
index b003458..930b945 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -116,3 +116,31 @@ pub fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
         util::slice_assume_init_mut(dest)
     })
 }
+
+/// Get random `u32` from the system's preferred random number source.
+///
+/// # Examples
+///
+/// ```
+/// # fn main() -> Result<(), getrandom::Error> {
+/// let rng_seed = getrandom::u32()?;
+/// # Ok(()) }
+/// ```
+#[inline]
+pub fn u32() -> Result<u32, Error> {
+    backends::inner_u32()
+}
+
+/// Get random `u64` from the system's preferred random number source.
+///
+/// # Examples
+///
+/// ```
+/// # fn main() -> Result<(), getrandom::Error> {
+/// let rng_seed = getrandom::u64()?;
+/// # Ok(()) }
+/// ```
+#[inline]
+pub fn u64() -> Result<u64, Error> {
+    backends::inner_u64()
+}
diff --git a/src/util.rs b/src/util.rs
index 9eeba9a..4a55b0a 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,5 +1,6 @@
 #![allow(dead_code)]
-use core::{mem::MaybeUninit, ptr};
+use crate::Error;
+use core::{mem::MaybeUninit, ptr, slice};
 
 /// Polyfill for `maybe_uninit_slice` feature's
 /// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have
@@ -46,3 +47,36 @@ fn ptr_from_mut<T: ?Sized>(r: &mut T) -> *mut T {
 fn ptr_from_ref<T: ?Sized>(r: &T) -> *const T {
     r
 }
+
+/// Default implementation of `inner_u32` on top of `fill_uninit`
+pub fn inner_u32() -> Result<u32, Error> {
+    let mut res = MaybeUninit::<u32>::uninit();
+    // SAFETY: the created slice has the same size as `res`
+    let dst = unsafe {
+        let p: *mut MaybeUninit<u8> = res.as_mut_ptr().cast();
+        slice::from_raw_parts_mut(p, core::mem::size_of::<u32>())
+    };
+    crate::fill_uninit(dst)?;
+    // SAFETY: `dst` has been fully initialized by `imp::fill_inner`
+    // since it returned `Ok`.
+    Ok(unsafe { res.assume_init() })
+}
+
+/// Default implementation of `inner_u64` on top of `fill_uninit`
+pub fn inner_u64() -> Result<u64, Error> {
+    let mut res = MaybeUninit::<u64>::uninit();
+    // SAFETY: the created slice has the same size as `res`
+    let dst = unsafe {
+        let p: *mut MaybeUninit<u8> = res.as_mut_ptr().cast();
+        slice::from_raw_parts_mut(p, core::mem::size_of::<u64>())
+    };
+    crate::fill_uninit(dst)?;
+    // SAFETY: `dst` has been fully initialized by `imp::fill_inner`
+    // since it returned `Ok`.
+    Ok(unsafe { res.assume_init() })
+}
+
+/// Truncates `u64` and returns the lower 32 bits as `u32`
+pub(crate) fn truncate(val: u64) -> u32 {
+    u32::try_from(val & u64::from(u32::MAX)).expect("The higher 32 bits are masked")
+}


Test patch:
diff --git a/tests/mod.rs b/tests/mod.rs
index 58e2295..204ec65 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -14,13 +14,32 @@ fn test_zero() {
     assert!(res.is_empty());
 }
 
+trait DiffBits: Sized {
+    fn diff_bits(ab: (&Self, &Self)) -> usize;
+}
+
+impl DiffBits for u8 {
+    fn diff_bits((a, b): (&Self, &Self)) -> usize {
+        (a ^ b).count_ones() as usize
+    }
+}
+
+impl DiffBits for u32 {
+    fn diff_bits((a, b): (&Self, &Self)) -> usize {
+        (a ^ b).count_ones() as usize
+    }
+}
+
+impl DiffBits for u64 {
+    fn diff_bits((a, b): (&Self, &Self)) -> usize {
+        (a ^ b).count_ones() as usize
+    }
+}
+
 // Return the number of bits in which s1 and s2 differ
-fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize {
+fn num_diff_bits<T: DiffBits>(s1: &[T], s2: &[T]) -> usize {
     assert_eq!(s1.len(), s2.len());
-    s1.iter()
-        .zip(s2.iter())
-        .map(|(a, b)| (a ^ b).count_ones() as usize)
-        .sum()
+    s1.iter().zip(s2.iter()).map(T::diff_bits).sum()
 }
 
 // TODO: use `[const { MaybeUninit::uninit() }; N]` after MSRV is bumped to 1.79+
@@ -55,6 +74,44 @@ fn test_diff() {
     assert!(d2 < 4500);
 }
 
+#[test]
+fn test_diff_u32() {
+    const N: usize = 1000 / 4;
+    let mut v1 = [0u32; N];
+    let mut v2 = [0u32; N];
+    for v in v1.iter_mut() {
+        *v = getrandom::u32().unwrap();
+    }
+    for v in v2.iter_mut() {
+        *v = getrandom::u32().unwrap();
+    }
+
+    // Between 3.5 and 4.5 bits per byte should differ. Probability of failure:
+    // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500]
+    let d1 = num_diff_bits(&v1, &v2);
+    assert!(d1 > 3500);
+    assert!(d1 < 4500);
+}
+
+#[test]
+fn test_diff_u64() {
+    const N: usize = 1000 / 8;
+    let mut v1 = [0u64; N];
+    let mut v2 = [0u64; N];
+    for v in v1.iter_mut() {
+        *v = getrandom::u64().unwrap();
+    }
+    for v in v2.iter_mut() {
+        *v = getrandom::u64().unwrap();
+    }
+
+    // Between 3.5 and 4.5 bits per byte should differ. Probability of failure:
+    // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500]
+    let d1 = num_diff_bits(&v1, &v2);
+    assert!(d1 > 3500);
+    assert!(d1 < 4500);
+}
+
 #[test]
 fn test_small() {
     const N: usize = 64;