-
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
Rand crate revision (pre-stabilisation) #2106
Conversation
Unfortunately this section has as many questions as answers
There's no reason for RNGs to not also have an If we're going to have Obviously I support the simpler version of a As to using a If we're having userspace PRNGs thare are not CSPRNGs, then we should absolutely add the PCGen and Xoroshiro PRNGs. Xoroshiro is even faster than pcg, but it's got Edit: it's got some unfortunate trouble with the lowest bit, so we'd have to be sure to override next_bool or equivalent with that generator. |
Agreed
I think there still are lifetime problems actually, if you want
I think you mean the Bernoulli distribution.
Yeah, I do too now; it's simpler and all the distribution stuff is available anyway. Even the simpler version is redundant against
Sounds like you're not too familiar with the two
Sounds like a good plan to me. But I'm not sure everyone agrees, hence the question over splitting the crate into a minimal core and extras or not. Did you finish your last sentence BTW? |
Whoops. Fixed. I will reply a little more when i have more time later. |
No I'm talking about IntoIterator, the version that consumes an owned value. No lifetime issue because there's no lifetime because there's no reference. It's not something that will be used a whole lot, but it should be available if you have ownership and Rust doesn't allow orphan instances, so we'll have to include it in the crate.
Yeah, neat. This is good. Might want a name that people not familiar with math would recognize a little more, but that's fine. I guess you can just say in the rustdoc what it does.
I was thinking mostly about the existing rand::distributions::range::Range. Hadn't seen the latest range update in the strawman proposal, if they're different at this point. As much as I hate trait soup, looking at range2 just now it feels like a good justification for a mild amount of it. It's very similar to how I did ranges for a game project over the weekend. Though, in my case the ranges produce values across The important part that we should keep in mind is in how easy it is to make new range implementations if you don't have a macro doing it for you. The macro can simplify the eight integral types and two float types, but it clearly wouldn't know how to handle a brand new GLRGBColor type (which would be three f32 values, each somewhere in the |
Regarding relation between This will allow us to keep As for splitting between crates, I personally think we should not keep algorithms in Small suggestion regarding pub trait SeedableRng: Rng {
type Seed;
fn reseed(&mut self, Seed);
fn from_seed(seed: Seed) -> Self;
} |
You still need to implement Regarding Bernoulli and other small distributions, this is how I've done it before. Here we could make use of the I don't know if you want to use @newpavlov the What was your other point @newpavlov, put @newpavlov using an associated type means each PRNG can only be seeded from one type... probably a good idea actually. Also see @Lokathor's point about removing |
With my limited trait-fu I only know how to implement impl Iterator for Rng {
type Item = u32;
fn next(&mut self) -> Option<Self::Item>{
Some(self.next_u32())
}
} but I do not know how to implement I at least know how to do it for specific structs that implement pub struct RngIter<T:Rng>(T);
impl <T:Rng> Iterator for RngIter<T>{
type Item = u32;
fn next(&mut self) -> Option<Self::Item>{
Some(self.0.next_u32())
}
}
// here, PCGen32 is a type that implements Rng
impl IntoIterator for PCGen32 {
type Item = u32;
type IntoIter = RngIter<PCGen32>;
fn into_iter(self) -> Self::IntoIter{
RngIter(self)
}
} And then I got stuck.
That sounds... overkill? In this specific case, you're always using the HalfOpen range deal, fn chance<R:Rng>(rng: &mut R, chance: f32) -> bool {
let roll: f32 = rng.gen();
roll < chance
} This is valid with the current
Merely an example to illustrate the fact that we have to have it be clear how you'd make new instances for new types. Saying "if you have a weird type that isn't really like a number you'll maybe have to make up stuff yourself" is totally a valid decision, as long as we write that down in the docs. |
Oh, you want an iterator which yields a stream of Using a trait is overkill when a simple function suffices? I guess. Does it matter? One of Rust's great strengths is that you don't pay for this kind of stuff, except via a tiny bit of compiler time. (In C++ you paid with your sanity when trying to understand compiler errors — Rust at least mostly fixed that. I don't know Haskell.) |
text/0000-rand-crate-redesign.md
Outdated
that implementing types are prefixed with `#[derive(Debug)]`, although in some | ||
cases it adds requirements on internal values. | ||
|
||
Is this useful? |
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.
Requiring Debug
definitely seems odd to me.
These traits can be added in the future without breaking compatibility, however | ||
they may be worth discussing now. | ||
|
||
### Creation of 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.
Without understanding too much more, it feels out-of-scope for Rng
to be dictating the way implementations are constructed. What's the motivation around new_from_rng
?
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.
new_from_rng
replaces impl Rand for XXX
from the current crate; e.g. see doc for ChaChaRng.
This is currently used to construct RNGs from OsRng
. Potentially it could also be used to construct RNGs from other PRNGs, but that's not always a good idea, and isn't used within the rand
crate anywhere, so new_from_rng
could be removed now that RNGs have new
seeding from OsRng
. This would remove a footgun but also remove functionality which might sometimes have a legitimate use (I've wanted to try making a deterministic parallel simulation a couple of times).
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.
impl Rand for PRNG
is pretty easy to understand, you use let mut newGen: Foo = rng.gen()
and the generics take care of it. I'm not sure why you'd want to remove it exactly.
On the other hand, most PRNGs will also be SeedableRng
, and any SeedableRng
is likely to be seedable from some type that has a Rand
instance. In that case you'd just write Foo::from_seed(rng.gen())
to get the same effect.
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.
Ah gotcha. Hmm, I'm in two minds about using a single trait for generating random values vs bootstrapping (is there a better term for that?) other generators.
At least it's a bit surprising to me on my first pass through the API. I like the idea of keeping those concepts separate by default, as the RFC suggests.
If we were going to stick with Rand
for constructing these generators from another one then I think the docs should call out that pattern explicitly.
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.
So to clarify, I like removal of Rand
, and by extension using it to build rngs.
I don't think we need to explicitly recommend generators follow a particular pattern and leave it up to the specific implementation to know what makes sense and what will be consistent with the generators provided by rand
.
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.
One thing that @dhardy said: you can put rustdoc on the methods of a trait implementation for a type, and when you look at that type's implementation list as you scroll down the page that rustdoc will appear on the correct methods. Im not sure if they meant something else by "Rust not allowing documentation of impls" or not.
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.
let mut newGen = NewGenType::from_seed(oldGen.gen());
I suspect this won't work for many RNGs: the implementations of SeedableRng
for ISAAC and ChaCha take a slice to seed from. Slices don't have a specified length in the type (like for example [u8; 4]
does) and are not implemented directly by Rand
or equivalent.
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 suspect this is how the coding session would go if that error came up:
let mut newGen = NewGenType::from_seed(oldGen.gen());
Compilation error? What the crap? Fine, Rust, if you want to be difficult I'll spell it out for you.
let seed: [u8;10] = oldGen.gen();
let mut newGen = NewGenType::from_seed(seed);
Ha, look, I'm smarter than a compiler.
The point is that if a user gets it into their head to make a generator from a generator, they're going to fight to do that more than they're going to look up in some docs somewhere why that's not a good idea. And if array10 (or whatever size) doesn't have a random instance then they'll make an array with zeroed and fill that up using a loop. They'll do whatever it takes to get the job done more than they'll stop and reconsider. This happens all the time with programmers, and this happens more often with people that are new to a field/library/problem domain/etc. because the person doesn't understand they they have an XY Problem on their hands.
It's just human nature, and you can't really fix it, but you can anticipate it and plan for it by making sure that the path of least resistance for this sort of obstinate scenario is one that's actually a safe solution.
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.
Why would someone find SeedableRng::from_seed
but not NewGenType::from_rng
, when they are trying to create a NewGenType
?
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 agree with you that we should suggest from_rng
be made when possible. KodrAus was the one against the idea of even suggesting that. Sorry for the confusion there.
Oh! Ha, an As to traits compared to simple functions: I don't mean that there is a runtime cost on the program's execution speed, I mean that there is a cognitive cost to the programmer. Every element of a program should be exactly as simple as possible for it to accomplish its task. "A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.", and all that sort of stuff. Last week we had this blog post in the news, and it's pretty much all true. A library can be as clever and smart as it wants to be but it's useless when people can't actually understand it and use it. Libraries like |
It turns out that both ISAAC and ChaCha RNGs accept a slice for initialisation. A slice ( Regarding removing |
Yes this one, I think that such approach is quite common, with
If I had in mind something like trait FeedableRng {
type Entropy;
fn feed_entropy(&mut self, entropy: Entropy)
} Not sure if |
@newpavlov @Lokathor what do you think of the following? Does it reduce cognitive load enough that using distributions is now okay? In the lib (feel free to suggest a new name): pub trait SampleFromRng {
fn sample<T, D: Distribution<T>>(&mut self, distr: D) -> T;
}
impl<R: Rng+?Sized> SampleFromRng for R {
fn sample<T, D: Distribution<T>>(&mut self, distr: D) -> T {
distr.sample(self)
}
} In user code: use rand::{thread_rng, SampleFromRng};
use rand::distributions::{Uniform, Range, Exp};
fn main() {
// use with a static Rng type:
let mut rng = thread_rng();
let _a: u32 = rng.sample(Uniform);
let _b = rng.sample(Range::new(-2, 15));
// or use with a dynamic Rng type:
let mut rng: &mut Rng = &mut thread_rng();
let _c = rng.sample(Exp::new(2.0));
} I guess we could add methods like |
@dhardy |
Just another thought on error handling here: #2191 |
I'm requesting that |
I did miss a lot of comments but I couldn't parse a conclusion from the rfc text. Did we reach a consensus regarding providing only opaque RNGs (std_rng, weak_rng, thread_rng, osrng,....) (stable across major versions)? |
Not really. There's some discussion here. There has also been quite a bit of talk about reproducible generators; these would obviously require names (e.g. |
Thanks. IMHO we shouldn't expose anything named. StdRng and WeakRng can be seedable/reproducible anyway. |
They can't be reproducible without adding a major restriction: not being able to replace the implementations with better PRNGs in the future. |
You can always guarantee they're stable across major versions. |
Isn't that what I just said above? But why? We've already got a proposal to replace Unless you mean allow breakage. But again why? Someone writes a scientific simulator, does an experiment, publishes a result, along with a link to let other people reproduce output. Someone else writes a game with procedurally generated maps. Symmetric encryption works the same way: permute using a fixed pseudo-random sequence (although obviously people select named CSPRNGs for that, like ChaCha20). Queue a major version update, and suddenly people have trouble reproducing the simulation result and players complain that their maps changed, and for no good reason. Reproducibility means guaranteeing to produce the same output. |
I'm not sure were we are misunderstanding each other but let's try again. I wholeheartedly agree that reproducibility is important, so let's get that out of the way. I'm arguing that |
So you're suggesting a half-way house? Well I have argued elsewhere that That still leaves open what you suggest, not having named generators like Sorry, been through this several times but too lazy to find the links because most of it is buried in the middle of a long comment thread like this one. Much better if you could open an issue to discuss stuff like this. |
@arthurprs In the world of Rust, going from 0.x to 1.x is usually seen as the last breaking change you should ever make. The mentality is that you should never need to go to 2.x, and certainly not for at least a few years after 1.x came out. |
@Lokathor there are significant disagreements whether Rust 2.x should ever exist. And while it would be feasible to release |
@dhardy Yes. If the user have a stronger reason to get algorithm X it can just use its crate. Consider WeakRng for example, there were almost half a dozen proposals to improve it. One can argue that the initial choice wasn't the best but its bound to happen again. If that kind of thing happen again the maintainers will have 3 choices (assume the PR has merits):
Note that 2 and 3 are potentially duplicating something from crates.io anyway, but with a blessing. |
@arthurprs what you say makes sense, so long as (a) the external crates aren't huge and (b) the external crates are sufficiently trustworthy (e.g. it may not be desirable to include a crate from an unknown author). But this is an important issue, and one I've already stepped around. Can you make a new issue to summarise please? (Easier for others to read.) |
dhardy I was trying to agree with you and somehow you still ended up disagreeing with me ?_? |
@Lokathor sorry; I frequently disagree with everyone ;-) No offence intended there; I like to break arguments down into small enough pieces that we can work out what we do/don't agree on :-) |
Since
|
It could be implemented as a method that adds entropy, rather than replacing it. |
That would be some entirely other trait. |
We already have an issue on seeding RNGs: dhardy/rand#18 |
I'm closing out this RFC, as work is instead happening directly in the |
Summary
Evaluate options for the future of
rand
regarding both cryptographic andnon-cryptographic uses.
There is a strawman revision which implements or demonstrates many of the changes
suggested in this document, but is not entirely in-sync with the suggestions
here.
Most current work is going on in the issue tracker and pull requests on this fork.
rand-core
RFC (subset; intended to be merged first)