Skip to content

Conversation

@dhardy
Copy link
Member

@dhardy dhardy commented Nov 17, 2025

  • Added a CHANGELOG.md entry

Summary

Removes assoc. type BlockRngCore::Item and removes bounds on Results.

Renames to Generator.

BlockRng{,64} is now more restrictive: e.g. it can't support Result = Vec<u32>. I think we could support the same bounds as previously (AsRef<[u32]> + Default), but we shouldn't. Anyway, #24 makes this irrelevant.

Motivation

#24 and the rngs PR made me realise that we do want a "generator" trait, especially for ReseedingRng. Hence e.g. Hc128Core should remain public.

I also realised that we can use the same BlockRng code over a much simpler trait.

Missing parts

Documentation

Obviously documentation needs some work. Better to wait until after #24 though (which will need to be rebased over this — shouldn't be too hard).

Provided impls

I'd also love to provide impls like this:

impl<G: Generator<Output = u32>> RngCore for G {
    fn next_u32(&mut self) -> u32 {
        let mut x = 0;
        self.generate(&mut x);
        x
    }

    fn next_u64(&mut self) -> u64 {
        le::next_u64_via_u32(self)
    }

    fn fill_bytes(&mut self, dst: &mut [u8]) {
        le::fill_bytes_via_next(self, dst)
    }
}

Sadly there's "one little issue": conflicting trait implementations. (Do I want to go on a rant about how Specialization/other-solutions-to-conflicting-trait-impls has been Rust's biggest missing feature for over a decade? Yes, but also no. Currently we can't even do this.)

So we work around that by having RNG crates impl both traits. (We could provide a set of macros to help, but probably doc-examples that can be copied are the better solution: there won't be much code anyway provided we keep the existing helper methods.)

Of course, RNG crates could still implement only the RngCore trait.

Questions

Should word-generators implement the Generator trait? In a way it's a nice interface, though we don't have any immediate applications. Perhaps combined with specialization it could lead to some optimizations (e.g. rust-random/rand#1286 is an example where the sample-size arguably should depend on the generator).

Drawbacks

rand_core has a lot of traits:

  • RngCore, CryptoRng
  • Generator, CryptoGenerator
  • TryRngCore, TryCryptoRng
  • SeedableRng

Is this too many?

@dhardy dhardy requested a review from newpavlov November 17, 2025 10:44
@newpavlov
Copy link
Member

If ReseedingRng is the only motivation for this PR, then, as I wrote in #24, I don't think we should bother with the block trait, especially considering that it would block us from removing the serde feature.

@dhardy
Copy link
Member Author

dhardy commented Nov 17, 2025

This doesn't stop us removing the serde feature. We could rebase #24 on top of this; the only real differences are that the Generator trait would remain and crates like rand_hc would continue to expose both a "core" and an RNG.

@dhardy
Copy link
Member Author

dhardy commented Nov 18, 2025

I'm inclined to go this path (this PR and #24 but keeping the Generator / CryptoGenerator traits).

The thing I like least is that Generator is implementable for word generators like Xoshiro but useless — "when" Rust supports something like specialization, specialization on the type of Generator::Result may actually be useful, but until then I think it's useless.

Comment on lines +37 to +40
/// The result type.
///
/// This could be a simple word like `u64` or an array like `[u32; 16]`.
type Result;
Copy link
Member Author

Choose a reason for hiding this comment

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

Should this be renamed? Most of the std::ops traits have an Output associated type, so that may be the more logical name.

/// }
/// }
/// ```
fn generate(&mut self, result: &mut Self::Result);
Copy link
Member Author

Choose a reason for hiding this comment

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

Generating by-reference has two limitations:

  • Using it requires first constructing a "default" instance. Since we don't even require Result: Default, this is only usable when the Result type is known or otherwise bounded. In practice this seems fine for our use-cases, but it could be an issue.
  • Using it to generate small, Copy types is unergonomic and dependent on inlining for good performance.

Alternative 1: return Self::Result by value. This is easier to use. For chacha20 (uses 2048-bit output blocks) it may be dependent on inlining optimisations for performance. (This type of optimisation is commonly relied on by constructors in Rust.)

Alternative 2: have both fns. Over-complex.

@newpavlov
Copy link
Member

newpavlov commented Nov 18, 2025

We could rebase #24 on top of this

I don't think it makes sense to have both the block traits and #24. We should either use the block traits and a generic block wrapper around them to implement RngCore (and thus keep the serde feature), or replace them with utility functions and user-defined buffer field.

My initial intent for introducing the block trait was simplification of implementation code by using type aliases (i.e. type MyRng = BlockRng<MyRngCore>;), but in practice all implementation crates (at least in this org) use BlockRng<MyRngCore> as a private field and re-implement the trait by manual delegation. Granted, the type alias approach has issues, so I understand why it's not used (and why we migrated to buffering macros in RustCrypto).

The block trait code results in a significant amount of useless boilerplate, both in rand_core and in implementation crates. Note that #24 and rust-random/rngs#78 significantly reduce the number of LoC, despite the former adding a lot of new docs.

Since you haven't provided any additional motivation for this PR except SeedableRng, I am against it. I will prepare a migration PR for rand to demonstrate that #24 works fine for SeedableRng.

@dhardy
Copy link
Member Author

dhardy commented Nov 19, 2025

Your arguments seem to be against keeping the BlockRng code. I modified that here since I wanted to push a clean PR (tests fine and can be reviewed independently) and it wasn't hard, but I wasn't intent on keeping the block code.

Since you haven't provided any additional motivation for this PR except SeedableRng, I am against it.

Motivation roughly coincides with direct utility of generator "cores". (There might be other motivations but I believe they are grossly limited by the lack of specialization, for now at least.) A search for ChaCha{8,12,20}Core shows a few custom uses of ReseedingRng, though not a huge number.

I will prepare a migration PR for rand to demonstrate that #24 works fine for SeedableRng.

Oh, it will work. I wonder whether there will be a noticeable performance hit; possibly not much since branch predictors are quite good. But if I remember correctly the current design was benchmark driven.

@newpavlov
Copy link
Member

See rust-random/rand#1686

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.

3 participants