Skip to content

Add custom-fallback Backend #684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

bushrat011899
Copy link

@bushrat011899 bushrat011899 commented Jun 5, 2025

Objective

Solution

As an alternative to #672, and based on feedback from @newpavlov (here) and @briansmith (here), this PR adds a new last-resort optional backend, custom-fallback. Unlike #672, this defines new external symbols specifically for the fallback backend, rather than re-using the existing symbol for custom. This avoids confusing linking errors when a user intends to provide a custom backend, but a fallback has already been provided.

As a precaution, the custom-fallback backend is behind a new feature of the same name. If the feature is not enabled, then the fallback symbols are not defined and it will not be used. As a further precaution, a RUSTFLAGS flag getrandom_no_custom_fallback can be set to make the usage of the custom-fallback a compiler error. Since this will only raise a compiler error when the fallback is used, users can add this flag liberally without needing to understand what targets getrandom officially supports.

To make defining fallback backends as ergonomic as possible, this PR adds an unsafe trait, Backend, and a macro set_backend. All backends now use this Backend trait rather than exporting functions directly. This is done for consistency and to encourage internal API decisions to be made with the external backends in mind. The trait is marked unsafe to highlight the implementer must provide CSPRNG-appropriate values. The methods themselves and marked safe, as getrandom has no safety contract it should uphold in order to call these methods.

Examples

Now, users and libraries can define a backend appropriate for use as a fallback without the use of RUSTFLAGS. First, the Backend trait must be implemented for some marker type:

//! A library crate "my_fallback"

use getrandom::Backend;

pub struct MyFallbackBackend;

unsafe impl Backend for MyFallbackBackend {
    #[inline]
    fn fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<(), getrandom::Error> {
        // ...
    }
}

Now, a user can choose to activate the custom-fallback feature from getrandom and use the backend with the set_backend macro:

//! The end-user's crate

use my_fallback::MyFallbackBackend;

getrandom::set_backend!(MyFallbackBackend);

Libraries may call this macro themselves, but since it will only be used when no other backend is available, and the way pruning at link-time works, it is highly encouraged that end-users set the backend themselves.

By using a trait as the interface for defining fallback backends, default methods for u32 and u64 can be provided by getrandom, but overridden by the fallback where appropriate. Since the external functions are not part of the public API, new methods can be added to Backend without a major release.


Notes

  • This PR comes from extensive discussion and feedback in the PR and issues linked in the Objective section, please see those for context on some of the design decisions made here.
  • A macro_rules! macro is used to set the backend. However, an attribute proc-macro could be used to provide an experience more similar to #[global_allocator] or #[panic_handler]:
    #[getrandom::set_backend]
    static BACKEND: MyCustomBackend = MyCustomBackend;
    However, this would require a new proc_macro crate so I defer that to a future PR (if at all)
  • The current optional backends may be better suited as external crates the user can include as a dependency (e.g., getrandom-js), but this would be a breaking change.
  • I have not changed how custom works as a backend to ensure this PR is not a breaking change. However, a future breaking change may want to replicate how custom-fallback works for custom, as it's a better user experience and allows overriding the u32 and u64 methods.

@bushrat011899
Copy link
Author

bushrat011899 commented Jun 5, 2025

I've added a CI test to ensure the fallback works as intended, including asserting that we should see a compiler error when the fallback is double-defined/not provided. You can view the results here.

Looking at the double-definition linking error (below), I think this is an acceptable message to receive. It clearly indicates the issue is within setting a backend, so appropriate documentation on the macro should make it straightforward for users to diagnose and resolve the issue.

   Compiling fallback_test v0.1.0 (/home/runner/work/getrandom/getrandom/fallback_test)
error: symbol `__getrandom_v03_fallback_fill_uninit` is already defined
  --> src/main.rs:47:5
   |
47 |     getrandom::set_backend!(ConstantBackend);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `getrandom::set_backend` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `fallback_test` (bin "fallback_test") due to 1 previous error

Note that I'm using an embedded target for this test, not wasm32-unknown-unknown, as missing symbols aren't caught until runtime on Wasm. Since this PR doesn't have special handling for Wasm I don't think it's necessary to add a more complex test harness.

@newpavlov
Copy link
Member

As I've wrote previously in #672, I don't think we need to introduce a trait. It's just a lot of needless complexity. Free-standing extern functions are sufficient.

Copy link
Member

@dhardy dhardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is superior to #672 in that it avoids the issue of a custom WASM-web fallback provided by some other library accidentally being incompatible with a custom backend.

Alongside this change we should provide a crate which depends on getrandom with the custom-backend feature and provides the JS/web backend.

Comment on lines +28 to +32
# Optional backend: custom-fallback
# This flag allows a custom fallback backend.
# This will be used as a last-resort instead of raising a compiler error when
# no other backend is available.
custom-fallback = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the documentation to say that this feature should only be enabled by a crate which provides a custom fallback.

Comment on lines +21 to +32
macro_rules! use_fallback_or {
($($tt: tt)*) => {
cfg_if! {
if #[cfg(feature = "custom-fallback")] {
mod fallback;
pub use fallback::Implementation;
} else {
$($tt)*
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This design (in addition to documentation on the custom_fallback feature flag) mostly solves one of my concerns: linker errors due to usage of an undefined function.

/// # Safety
///
/// Implementors must uphold the contracts of all methods provided by this trait.
pub unsafe trait Backend {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to a trait based backend adds a lot of noise to this PR. I don't have a strong preference etiher for or against, but it should be merged in a separate PR.

I think @newpavlov would prefer not using a trait at all. @bushrat011899 do you have motivation for it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale was that a trait can have default method implementations, but extern functions cannot. This allows the u32' and u64` methods to be potentially defined by a backend without adding boilerplate for backends which would just use the default implementation anyway.

Personally, I prefer this structure, and it lends itself well to extension in the future (e.g., maybe the backend trait could include the customised error messages instead of putting all the backend error types in a single file, etc.)

I do think if this PR is acceptable as a whole, the trait refactor should be split out and done first and then a simpler fallback PR can follow.

@dhardy
Copy link
Member

dhardy commented Jun 6, 2025

Concern (minor): introducing support for new targets will become a behavioural change. E.g. it is conceivable that introducing support for WASI-P3 would change the code paths used, and also possible that this would cause run-time failure.

Concern (minor): the error messages (especially for wasm32-unknown-unknown) don't make it obvious how best to support this target.

Question: should we deprecate the wasm_js feature (i.e. remove support from the main getrandom crate)? I think we should only provide a single implementation, and embrace this PR fully by providing that implementation via another crate (getrandom-js). (For now though we would only comment that wasm_js is deprecated.)

@bushrat011899
Copy link
Author

Concern (minor): introducing support for new targets will become a behavioural change. E.g. it is conceivable that introducing support for WASI-P3 would change the code paths used, and also possible that this would cause run-time failure.

That is the case, but I think the design intent here is clear; the backend you provide as a fallback is only used if there's no other choice. If the backend you're providing is preferrable to one getrandom could provide, then you should be using custom instead.

Concern (minor): the error messages (especially for wasm32-unknown-unknown) don't make it obvious how best to support this target.

Documentation around this approach will definitely need to be improved to make up for the reduced utility of the error messages. If we do create a getrandom-js crate, it might be worth linking to it in the error message. Could be a good middle-ground between including a backend in getrandom itself and leaving it exclusively as a 3rd party option.

Question: should we deprecate the wasm_js feature (i.e. remove support from the main getrandom crate)? I think we should only provide a single implementation, and embrace this PR fully by providing that implementation via another crate (getrandom-js). (For now though we would only comment that wasm_js is deprecated.)

In my opinion, yes. I would even go as far as to suggest moving most of the optional backends out of getrandom entirely, saving internal for backends which can be enabled by default. That would simplify the configuration of getrandom itself quite considerably and give a clear pathway for 3rd parties to work on support for new targets (publish a fallback, eventually roll into getrandom as the default when appropriate)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assume JS on wasm32-u-u with wasm_js feature? Support wasm32-u-u on web via patching Cargo.toml? Ergonomics improvements for alternative backends
3 participants