Repository navigation
Description
Can we have one generator to do it all (excepting reproducibility)? ThreadRng attempts to achieve this:
- It's fairly fast
- It only requires 320 bytes + overhead (
Rcandthread_local) per thread - It's unpredictable
- It has periodic reseeding
Put another way, there are reasons that this is sub-optimal:
SmallRngis significantly faster (very roughly double the performance) and much smaller (32 bytes) while being good enough for many usesThreadRngdoes not currently have forward secrecy. We could tack this on for approx 10-25% performance overhead.
Other limitations:
ThreadRngdoes not have built-in fork or snapshot+resume protection. This will not change; see e.g. ThreadRng and StdRng after a snapshot or checkpoint restore: a sentence for the docs? #1843.
Plan
Strong + weak sources
ThreadRng has quite a few users, so I do not believe we should just abandon it. Instead, I propose:
- Re-add
ReseedingRngnot as a generic wrapper but as a concrete struct overChaCha12with periodic reseeding and fast erasure. - Add
StrongRng(name?) as a handle to aMutex<ReseedingRng>in static memory (feature-gated, default off). - Add
SmallThreadRngas a thread-localSmallRngand mayberand::fast_rng()to access it (feature-gated, default off). - If possible,
#[deprecate]the existingThreadRngand methods after adding replacements before the next breaking release.
Note: the fast-erasure (forward secrecy) addition requires breaking changes to rand_core. The rest should be possible to add in a patch release. See: RustCrypto/stream-ciphers#578
Note: this is very much a tentative plan. Feedback welcome!
Alternatives
There is an alternative strong source: SysRng. This is slower than ThreadRng (potentially much much slower, depending on the platform) but (depending on the application) may still be sufficiently performant and does not have the same security limitations as ThreadRng.
There is an alternative where fast generation is required: make a local PRNG, e.g. using make_rng. Limitation: constructing and initializing the PRNG using a system seed will not be very fast, hence this is only really useful where a significant amount of data must be generated or the user passes this PRNG to multiple use-sites. I suspect this would be inconvenient for many games; on the other hand it is already a necessary step to achieve reproducibility.
Activity
LiosK commentedon Aug 21, 2026
As for
SmallThreadRng,let mut rng: rand::rngs::SmallRng = rand::make_rng();generally looks sufficient to me because the memory footprint is minimal, and the initialization is fast ifThreadRngis already initialized.rand::fast_rng()returning an initializedSmallRngwould be handy to omit type annotations and performance-favorable if it pulls seeds fromgetrandomdirectly to skip initializingThreadRngwhen not done yet. I do not really see the benefit of sharing aSmallRngstate within a thread.LiosK commentedon Aug 21, 2026
As for fast erasure, the natural approach seems to be publishing the
FastErasureCore(name?) and replacing the currentThreadRng'stype Core = chacha20::ChaChaCore<chacha20::R12, chacha20::variants::Legacy>;with it without deprecating the existingThreadRng. Whether to retire the existingThreadRngfeels like a separate question, tied more to whether it should stay thread-local or move to being process-wide. Either way, exposing theThreadRng-ish internals separately would be great for users who want to re-implement whichever mode isn't provided by default, which is the motivation behind #1750.dhardy commentedon Nov 2, 2026
On further reflection, I find the simpler approach of simply removing
ThreadRngattractive (this was suggested by @newpavlov a long time ago). We can spin off arand-thread-rngcrate if necessary.