Skip to content

Commit 2e15731

Browse files
Add extern_item_impls opt-in backend (#786)
1 parent 698db3b commit 2e15731

File tree

7 files changed

+140
-11
lines changed

7 files changed

+140
-11
lines changed

.github/workflows/tests.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ jobs:
7878
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
7979
run: cargo test --features=std,sys_rng
8080

81+
extern_item_impls:
82+
name: Extern Item Implementations
83+
runs-on: ubuntu-24.04
84+
steps:
85+
- uses: actions/checkout@v6
86+
- uses: dtolnay/rust-toolchain@master
87+
with:
88+
toolchain: nightly
89+
- uses: Swatinem/rust-cache@v2
90+
- env:
91+
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls"
92+
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="extern_item_impls"
93+
run: cargo test --features=std --test mod extern_item_impls
94+
8195
ios:
8296
name: iOS Simulator
8397
runs-on: macos-14

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ wasm-bindgen-test = "0.3"
9494
[lints.rust.unexpected_cfgs]
9595
level = "warn"
9696
check-cfg = [
97-
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported"))',
97+
'cfg(getrandom_backend, values("custom", "efi_rng", "rdrand", "rndr", "linux_getrandom", "linux_raw", "windows_legacy", "unsupported", "extern_item_impls"))',
9898
'cfg(getrandom_msan)',
9999
'cfg(getrandom_test_linux_fallback)',
100100
'cfg(getrandom_test_linux_without_fallback)',

README.md

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,17 @@ or `js-sys` on "unknown" WASM targets then it's acceptable to enable this featur
9999
`getrandom` also provides optional (opt-in) backends, which allow users to customize the source
100100
of randomness based on their specific needs:
101101

102-
| Backend name | Target | Target Triple | Implementation
103-
| ----------------- | -------------------- | ------------------------ | --------------
104-
| `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
105-
| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls instead of `libc`.
106-
| `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
107-
| `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
108-
| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] with `EFI_RNG_ALGORITHM_RAW` (requires `std` and Nightly compiler)
109-
| `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`]
110-
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
111-
| `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend])
102+
| Backend name | Target | Target Triple | Implementation
103+
| ------------------- | -------------------- | ------------------------ | --------------
104+
| `linux_getrandom` | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call (without `/dev/urandom` fallback). Bumps minimum supported Linux kernel version to 3.17 and Android API level to 23 (Marshmallow).
105+
| `linux_raw` | Linux, Android | `*‑linux‑*` | Same as `linux_getrandom`, but uses raw `asm!`-based syscalls instead of `libc`.
106+
| `rdrand` | x86, x86-64 | `x86_64-*`, `i686-*` | [`RDRAND`] instruction
107+
| `rndr` | AArch64 | `aarch64-*` | [`RNDR`] register
108+
| `efi_rng` | UEFI | `*-unknown‑uefi` | [`EFI_RNG_PROTOCOL`] with `EFI_RNG_ALGORITHM_RAW` (requires `std` and Nightly compiler)
109+
| `windows_legacy` | Windows | `*-windows-*` | [`RtlGenRandom`]
110+
| `custom` | All targets | `*` | User-provided custom implementation (see [custom backend])
111+
| `unsupported` | All targets | `*` | Always returns `Err(Error::UNSUPPORTED)` (see [unsupported backend])
112+
| `extern_item_impls` | All targets | `*` | User or library provided custom implementation (see [externally implemented interface])
112113

113114
Opt-in backends can be enabled using the `getrandom_backend` configuration flag.
114115
The flag can be set either by specifying the `rustflags` field in [`.cargo/config.toml`]:
@@ -201,6 +202,37 @@ unsafe extern "Rust" fn __getrandom_v03_custom(
201202
}
202203
```
203204

205+
### Externally Implemented Interface
206+
207+
Using the nightly-only feature [`extern_item_impls`](https://github.com/rust-lang/rust/issues/125418)
208+
it is possible to provide a custom backend for `getrandom`, even to override
209+
an existing first-party implementation. First, enable the `extern_item_impls`
210+
opt-in backend to allow usage of this nightly feature. Then, you may provide
211+
implementations for `fill_uninit`, `u32`, and/or `u64` with an attribute macro
212+
from the `implementation` module.
213+
214+
```rust
215+
use core::mem::MaybeUninit;
216+
217+
#[cfg(getrandom_backend = "extern_item_impls")]
218+
#[getrandom::implementation::fill_uninit]
219+
fn my_fill_uninit_implementation(
220+
dest: &mut [MaybeUninit<u8>]
221+
) -> Result<(), getrandom::Error> {
222+
// ...
223+
Ok(())
224+
}
225+
```
226+
227+
For further details on what a suitable implementation for `fill_uninit` may look
228+
like, see [custom backend].
229+
230+
`getrandom` will provide a default implementation for `u32` and `u64`, but does
231+
not currently provide a default for `fill_uninit`, even if one is normally
232+
available for the current target. If no implementation is available,
233+
a compilation error will be raised with instructions for how to provide
234+
an implementation.
235+
204236
### Unsupported backend
205237

206238
In some rare scenarios, you might be compiling this crate for an unsupported
@@ -373,6 +405,7 @@ dual licensed as above, without any additional terms or conditions.
373405
[WASI]: https://github.com/WebAssembly/WASI
374406
[Emscripten]: https://emscripten.org
375407
[opt-in]: #opt-in-backends
408+
[externally implemented interface]: #externally-implemented-interface
376409

377410
[//]: # (licenses)
378411

src/backends.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ cfg_if! {
3232
} else if #[cfg(getrandom_backend = "unsupported")] {
3333
mod unsupported;
3434
pub use unsupported::*;
35+
} else if #[cfg(getrandom_backend = "extern_item_impls")] {
36+
pub(crate) mod extern_item_impls;
37+
pub use extern_item_impls::*;
3538
} else if #[cfg(all(target_os = "linux", target_env = ""))] {
3639
mod linux_raw;
3740
pub use linux_raw::*;

src/backends/extern_item_impls.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! An implementation which calls out to an externally defined function.
2+
use crate::Error;
3+
use core::mem::MaybeUninit;
4+
5+
/// Declares this function as an external implementation of [`fill_uninit`](crate::fill_uninit).
6+
#[eii(fill_uninit)]
7+
pub(crate) fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error>;
8+
9+
/// Declares this function as an external implementation of [`u32`](crate::u32).
10+
#[eii(u32)]
11+
pub(crate) fn inner_u32() -> Result<u32, crate::Error> {
12+
crate::util::inner_u32()
13+
}
14+
15+
/// Declares this function as an external implementation of [`u64`](crate::u64).
16+
#[eii(u64)]
17+
pub(crate) fn inner_u64() -> Result<u64, crate::Error> {
18+
crate::util::inner_u64()
19+
}

src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![doc = include_str!("../README.md")]
1111
#![cfg_attr(docsrs, feature(doc_cfg))]
1212
#![cfg_attr(getrandom_backend = "efi_rng", feature(uefi_std))]
13+
#![cfg_attr(getrandom_backend = "extern_item_impls", feature(extern_item_impls))]
1314

1415
#[macro_use]
1516
extern crate cfg_if;
@@ -34,6 +35,32 @@ pub use sys_rng::SysRng;
3435

3536
pub use crate::error::{Error, RawOsError};
3637

38+
/// Provides Externally Implementable Interfaces for the core functionality of this crate.
39+
/// This allows `getrandom` to provide a default implementation and a common interface
40+
/// for all crates to use, while giving users a safe way to override that default where required.
41+
///
42+
/// Must be enabled via the `extern_item_impls` opt-in backend, as this functionality
43+
/// is currently limited to nightly.
44+
///
45+
/// # Examples
46+
///
47+
/// ```rust
48+
/// # use core::mem::MaybeUninit;
49+
/// # #[cfg(getrandom_backend = "extern_item_impls")]
50+
/// #[getrandom::implementation::fill_uninit]
51+
/// fn my_fill_uninit_implementation(
52+
/// dest: &mut [MaybeUninit<u8>]
53+
/// ) -> Result<(), getrandom::Error> {
54+
/// // ...
55+
/// # let _ = dest;
56+
/// # Err(Error::UNSUPPORTED)
57+
/// }
58+
/// ```
59+
#[cfg(getrandom_backend = "extern_item_impls")]
60+
pub mod implementation {
61+
pub use crate::backends::extern_item_impls::{fill_uninit, u32, u64};
62+
}
63+
3764
/// Fill `dest` with random bytes from the system's preferred random number source.
3865
///
3966
/// This function returns an error on any failure, including partial reads. We

tests/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,36 @@ mod custom {
291291
assert!(res.is_err());
292292
}
293293
}
294+
295+
#[cfg(getrandom_backend = "extern_item_impls")]
296+
mod extern_item_impls {
297+
use core::mem::MaybeUninit;
298+
use getrandom::Error;
299+
300+
// This implementation for fill_uninit will always fail.
301+
//
302+
// WARNING: this custom implementation is for testing purposes ONLY!
303+
304+
#[getrandom::implementation::fill_uninit]
305+
fn my_fill_uninit_implementation(_dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
306+
Err(Error::new_custom(4))
307+
}
308+
309+
// This implementation returns a fixed value to demonstrate overriding defaults.
310+
//
311+
// WARNING: this custom implementation is for testing purposes ONLY!
312+
313+
#[getrandom::implementation::u32]
314+
fn my_u32_implementation() -> Result<u32, Error> {
315+
// Chosen by fair dice roll
316+
Ok(4)
317+
}
318+
319+
// Test that enabling the custom feature indeed uses the custom implementation
320+
#[test]
321+
fn test_extern_item_impls() {
322+
let mut buf = [0u8; 123];
323+
assert_eq!(getrandom::fill(&mut buf), Err(Error::new_custom(4)));
324+
assert_eq!(getrandom::u32(), Ok(4));
325+
}
326+
}

0 commit comments

Comments
 (0)