-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: new rand-core
crate, rand
adaptations
#2152
Conversation
I want to suggest to rename the |
I know that some Rngs can seek, but how many of them? The seeking trait seems like an easy thing to add later if we can't come to a quick conclusion. Individual rngs are also allowed to provide methods beyond just the traits as well, until then. Can the Error type be an associated type? That way each rng can pick their own enum of how they wish to fail. That would only work with design 2 or design 3, but it seems good if either of those are picked. And for default methods, if we use options 2 or 3, then next_u64 can default to two next_u32 calls. We would not need a default for try_fill, but we could also have that default to filling the bytes with next_u64 calls I suppose. |
@manuels I know it sounds cavalier, but I'm not so concerned, since (a) the documentation should recommend use of @Lokathor "Can the Error type be an associated type?" Technically I guess yes, except for uses not on |
I think the comment by @manuels is in line with Rust's tendency to make unsafe names longer, and dangerous-sounding. It's hard to miss that you've done something less safe. |
We figured out that I noticed the existing inherent method for seek on |
I feel the best design would be something like this. (your designs cover it only partially) The main advantage that we naturally solve But the big disadvantage is that it relies on two currently unstable features. As written in the RFC we can replace never type with blank enum, but I don't think we should stabilize Regarding default implementations, I started to think that it will be better to include default implementation for About Question about Also please clarify the naming: do you suggest to use |
I noticed that the |
My thoughts about the If we are talking about stronger RNGs, or even cryptographic ones, there is sometimes the suggestion to reseed the RNG. That may just be overly cautious, but that is not for me to decide. In that case it would be best if this function was more similar to I think there is nothing lost if |
Removing
Associated error type (@newpavlov): seems excessively complicated to me. Also "whether an RNG is fallible" seems pretty unimportant, given that error rates will be extremely low. Finally, you wrote a generic function, |
@dhardy As for names, I think |
Initially I'd imagined I kinda wondered if I suppose |
Support for seeding from an u64 ( Seeding with an u64 should result in a RNG with a chance of 2^64 of being different from one other generator. If we look at the birthday problem, there is a 50% chance of getting the same generator a second time after 2^32 random initializations. This drops quickly to only a couple of percents for 2^30. These numbers are very large, you would need about 8gb to just store the state of all the generators!. So initializing with an u64 seems good enough for almost any purpose to me. Initializing from an u32 would give a change of ~1% to get the same generator after ~2^14 (not checked the numbers here). So after about 15.000 initializations. If we want to support Initializing with an u64 instead of as many bits as a RNGs state would also allow one to spare the A disadvantage is that it makes it more difficult to write a correct initialization routine, but I think this is for most generators a solved problem. This are all not very important arguments. The arguments in the RFC to include it for reproducibility are best. Seeding by slices: I do like the proposals for an associated type or I don't really see a use case for implementing So my preferred trait would look like:
|
@burdges I don't fully understand what you are saying about reseeding, can you explain it some more? On the other hand, if there is such an RNG, it can just as well expose such a function without it being part of the |
@pitdicker "adding entropy" is pretty easy: just XOR the current state with new bits. Obviously there is a maximum amount of entropy the state can hold, so any definition of "add entropy" for a fixed size type must allow wastage. The point is simply that if somehow the "new entropy" is not fully trusted but the state was previously unknown, the new state is still unknown. About your pub trait SeedableRng: Rng {
type Seed;
fn from_seed(&mut self, Self::Seed);
fn seed_from_u64(&mut self, seed: u64);
} @newpavlov take a look at this. It already has to deal with two different sources of errors ( |
@dhardy you are completely right :-) |
Let me say first: It is great to see some traits proposed for new functionality like seeking and streams! The biggest advantage they offer is the ability to create new RNGs of the same type, with no overlap, and without the need to get a new seed (from something like the OS). I think this is something very useful. The link in the RFC to the PCG blog gives some great background info. Creating a new RNG from an existing one of the same type is something that I think is always possible. I would not change the traits to much to accommodate for the design of PCG. I don't know if this is possible, but I would implement it as part of the
I was reasonably enthusiastic about seeking, until looking into it some more. I have not looked into it to deeply, but ChaCha seems to support jumping from an absolute position, and PCG only from the current position (unless you store the initial state somewhere). One other thing that could be a problem: the amount it is possible to seek, is the same as the size of the period of the RNG, and maybe even larger (wrapping around). What to do with RNGs with periods like 128-bits, 256, 1024 or even larger? On the other hand, jumps that large seem only useful for the PCG party tricks... For any normal use jumping by an u64 seems sufficient (just like a period of 2^64 seems sufficient for a single stream of an RNG). Next I tried to come up with some use cases. I don't have any real ones yet. It is something I have not seen other random libraries in other programming languages expose. You already question if a trait for seek support should be included. I would say, lets wait until we have a few implementations to see what the possibilities of different RNGs are. |
@pitdicker the only use-case I can personally see for seeking is to get a different stream (I think this is basically what As for multiple streams, there are a bunch of ways to make them, as you say. I came up with a use-case for multiple streams a long time back: threading in agent-based sims where reproducibility is important. Given hundreds to millions of agents each undergoing stochastic processes and each needing a CPU-intensive update each step of the simulation, it is desirable to distribute the updates between CPU cores by giving each agent (or group of agents) its own PRNG; this could work several different ways (new agents deriving from a parent agent or new agents generated in-order by a master generator) and still be reproducible. |
I agree, a big plus for multiple streams. Seeking by arbitrary amounts not so much yet. What do you think about using |
I'm not sure; your I guess you mean to use specialisation to provide another way of constructing each generator from itself. This would require the |
Then a seperate trait would be better, something like this?
|
We should probably work out how At first blush, we seemingly need this We could make this all resemble hashers more closely and pump some life back into
We could maybe give
ChaChaRng boosts if its counter exceeds some constant. PRNGs that do not use a counter cannot know when to boost, but if any exist then some wrapper could give them a counter. We could split
I donno if using a closure for flow control like that will always be as efficient as folks want, but presumably yes once it's wrapped and monomorphized inside |
@pitdicker wrote:
I disagree! When dealing with a CSPRNG you should reseed the new RNG! If you just seek, the two RNG are correlated and somebody might be able (maybe by just reading enough information from the first RNG) to get information about the state of the other RNG. Additionally, I have just heard arguments against renaming Just put
in your rust file and everybody knows with what kind of RNG we are dealing with. |
The point of both my examples is that (a vocal minority of) people are increasingly interested in ability to handle errors, even if they are unlikely to happen (or only happen in uncommon configurations). I’m re-reading the updated RFC right now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the new error kind enum.
*For now*, re-export all the above in the `rand` crate. Also add two things to | ||
`rand`: | ||
|
||
* the `NewSeeded` trait and its implementation for `SeedFromRng` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence is slightly confusing. You don’t implement trait for a trait. Was this sentence supposed to read as one of the following?
- a
trait NewSeeded: SeedFromRng
;NewSeeded
, a supertrait ofSeedFromRng
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. See this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then
the
NewSeeded
trait and its (blanket) implementation forSeedFromRng
implementors
would be less ambiguous.
|
||
* How end-users should use `rand` (for now, the assumption is that all | ||
necessary items will be re-exported through `rand` to minimise breakage; | ||
this may change but will be the subject of a follow-up RFC, not this one) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest deciding in this RFC whether we’re interested in keeping the reexport or would rather make the reexports deprecated somehow. There’s two sides to this. First, if we are interested in deprecation, it should land together with the split so hopefully we don’t have to go through the big breaking churn twice. Second, this would draw the guideline for the external RNG implementations. If those do “stuff” wrong, without being aware of our intentions for the rand/rand-core/etc, it will make churn ever so slightly worse.
I personally think re-exporting rand-core traits from rand
(and crates implementing RNGs in general) is a significant usability boost – you wouldn’t need to extern crate
two crates and be able to do extern crate nicerng::{NiceRng, Rng}
for any RNG implementor you happen to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I agree with you, pretty well, and created this issue for discussion.
text/0000-rand-core.md
Outdated
let mut r = OsRng::new()?; | ||
Self::from_rng(&mut r) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such implementation essentially makes it impossible to implement NewSeeded
by anybody else for any other seedable RNG.
That is, it is impossible to write something along the lines of
struct DifferentlySeededRng(MyRng);
impl SeedFromRng for DifferentlySeededRng {...}
impl NewSeeded for DifferentlySeededRng {
fn new() -> Result<Self> { ... }
}
due to coherence, which makes this trait hardly useful IMO. If we’re adding a trait+impl like this, we should aim to make fn new
a default fn new
as soon as specialisation is stable… but I personally think something like this:
impl OsRng {
fn seed_new<R: SeedFromRng>() -> Result<R>;
}
is potentially way better and is a pattern that’s extensible to any number of seed-capable RNGs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively a plain function could exist in rand
without any of that unnecessary trait stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trait I suggested is there to allow this:
use rand::NewSeeded;
let rng = SomePRNG::new();
I suppose let rng = OsRng::seed_new::<SomePRNG>();
isn't a terrible alternative.
To be honest, I don't see why anyone would need to write an alternative implementation for NewSeeded
though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow-up comments in this issue please.
cc @briansmith |
@aturon no, we're not really working from the RFCs now. Some of the plans are the same, some have changed; we're getting feedback via PRs now which has the advantage of more detail but perhaps the disadvantage of less audience. Since |
@dhardy OK, that's pretty much as I had assumed, and is totally fine. It's wonderful to see For the time being, I'm going to close out these RFCs. Those interested in |
This RFC seeks to introduce a
rand_core
trait, and make a few changes torand
for compatibility.Rendered link
Parent RFC — for reference on the development of this RFC
Still undecided in this RFC:
CryptoRng
Error
typeSeedableRng
stream supportnext_u128
?