Skip to content
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

Add a module with helper functions #989

Open
newpavlov opened this issue Jun 23, 2020 · 10 comments
Open

Add a module with helper functions #989

newpavlov opened this issue Jun 23, 2020 · 10 comments

Comments

@newpavlov
Copy link
Member

newpavlov commented Jun 23, 2020

One of the major rand critiques, which I've recently seen, argues that it makes hard to write "quick and dirty" code. This motivates creation of crates like fastrand, which could lead to an unfortunate ecosystem split and overall code duplication.

We already have the random helper function and I think it could be worth to add other helper functions as well. To make docs less cluttered we probably should keep them in a separate top-level module (something like helpers?). With the potential migration to ChaCha8/12, those functions can be simple wrappers around thread_rng and be fast enough for all, but most demanding use-cases. One of side-effects of using helpers is that trait imports are no longer needed for simple use-cases, so it could be seen as a work-around until something like rust-lang/rfcs#2375 gets added to the language.

A preliminary list of helper functions:

We could tweak naming a bit to make it a bit more natural.

cc @stjepang

@dhardy
Copy link
Member

dhardy commented Jun 25, 2020

Is this primarily because rand::thread_rng().shuffle(&mut v) is too long to write or because this is too hard to learn?
Or is it (for whatever reason) because the rand crate is too big with too many dependencies?

We should be careful about adding more convenience functions (complicating the API for ease-of-use), and also realise that we can't be all-things-to-everyone (some people really care about minimising dependencies/size for whatever reason; rand is limited here by the requirement for a cryptographically-secure default generator).

@vks
Copy link
Collaborator

vks commented Jun 25, 2020

One of the major rand critiques, which I've recently seen, argues that it makes hard to write "quick and dirty" code. This motivates creation of crates like fastrand, which could lead to an unfortunate ecosystem split and overall code duplication.

There are a lot of crates like this, implementing some subset of Rand's functionality. There does not seem to be any convergence on a "quick and dirty" randomness crate, with every author creating their own variant. I don't see what we can do about this code duplication.

How popular are these crates? I'm not sure that there is a risk of an ecosystem split.

Looking at fastrand, it seems the main API simplification is getting rid of thread_rng() by making it implicit (like rand::random) and replacing Rng::gen<T> with rand::T for primitive types. I agree with @dhardy that this overlaps with Rng, which is already a wrapper for convenience. Having both seems too much? If most use cases don't require an RNG different from thread_rng(), we could replace Rng with the simplified API.

@vks vks closed this as completed Jun 25, 2020
@vks vks reopened this Jun 25, 2020
@newpavlov
Copy link
Member Author

Don't forget that helper functions not only shorter (due to the implicitness of thread_rng()), but also don't require traits import. I think it makes easier for novice Rust programmers to start using rand and reduces a certain amount of paper-cuts for experienced ones. Also it's a bit strange to have only a single helper function, which arguably is not overwhelmingly more popular than other ones.

Since those functions are trivial and will be kept in a separate module, I don't think they will be perceived as an API complication and additional maintenance burden will be minuscule.

Judging by a cursory look at these search results, I would say that the proposed helper functions will cover more than half of explciit thread_rng() uses. (It would be nice to have a more thorough analysis, but it would take a significant amount of time...)

overlaps with Rng, which is already a wrapper for convenience.

I wouldn't call it a convenience wrapper. It's an extension trait which implements additional functionality.

@vks
Copy link
Collaborator

vks commented Jun 25, 2020

I wouldn't call it a convenience wrapper. It's an extension trait which implements additional functionality.

Not really, it only wraps Distribution and RngCore methods.

@dhardy
Copy link
Member

dhardy commented Jun 25, 2020

There is also good reason to keep Rng even if we do add these "helper functions", since Rng is also useful on other RNGs. Often we just tell people to use rand::prelude::*; in which case the trait import isn't important.

Since those functions are trivial and will be kept in a separate module

The main drawback IMO is that in the docs there will be a long-ish section at the bottom of all these functions. But random already fits in with this list perfectly leaving only thread_rng as the slightly-obscured "vitcim", so it's not a big drawback.

I wouldn't want to remove thread_rng since it has other uses (also from_rng):

let mut rng = thread_rng();
let dist = Uniform::from(1..11);
let sample = dist.sample(&mut rng);

Conclusion: I see only very minor advantages and drawbacks — so either option sounds acceptable.

@vks
Copy link
Collaborator

vks commented Jul 1, 2020

So to resolve this, we would have to implement the following?

Via thread_rng():

  • rand::T for primitive integers T
  • rand::shuffle
  • rand::digit
  • rand::{alphanumeric, alphabetic, lowercase, uppercase}

New methods for Rng:

  • Rng::T for primitive integers T (note the subtle difference between Rng::bool and Rng::gen_bool)
  • Rng::shuffle (equivalent to SliceRandom::shuffle)
  • Rng::digit
  • Rng::{alphanumeric, alphabetic, lowercase, uppercase}

@vks vks mentioned this issue Aug 5, 2020
19 tasks
@dhardy
Copy link
Member

dhardy commented Aug 28, 2020

My opinion is that we should not attempt to "resolve" this issue without more feedback on what users want and why. Of the above, I don't see anything wrong with adding Rng::shuffle. We could also add rand::rng as an alias for thread_rng.

Adding methods like rand::i16 — the question is really where do you stop? IMO it would mostly just be clutter in the docs.

One thing we can't compete with fastrand on is being very small, simple and zero-dependency. For some people/cases that is a valid feature (probably mainly because people like using something they can understand).

Ironically the selling point of fastrand is not its speed so much as its simplicity.

@dhardy
Copy link
Member

dhardy commented Nov 19, 2020

I was hoping more people might pitch in with what they would / would not like to see. So, here's my opinion (again):

  1. Adding Rng::shuffle (for slices) might be okay, but if we did, then what about Rng::choose and Rng::choose_multiple?
  2. Other methods seem mostly fillers
  3. We don't have some of the above generators such as digit, alphabetic, lowercase and uppercase, but do we want them?

Regarding (1): The choose methods are implemented on both slices and iterators under other traits (SliceRandom and IteratorRandom), but supporting both on Rng would require a new trait (impl'd for I: Iterator and &[T]). More convenience for significantly more complexity; is that worth it?

Regarding (3): Should we impl SampleUniform on char? If we did, this would enable things like rng.gen_range('A'..='Z'), but there is a "hole" in the char range (0xD800..=0xDFFF) which should never be generated. Maybe the hole isn't a big deal though (just an awkward impl).

@sicking
Copy link
Contributor

sicking commented Dec 1, 2020

I've always felt that Rust needs a crate for "simple" random number generation. It's definitely hard to draw the line for exactly what to include in such a crate though.

To answer the question in #989 (comment), I think the goal of such a crate would primarily be "easy to learn" rather than "few characters to type". To accomplish this I would heavily bias towards having few functions in the API.

My proposal would be to include just

  • Generate u32 in range [0, N)
  • Generate f64 in range [0, 1)
  • Generate integer type (maybe including bool, but not including char)
  • Shuffle array
  • Create explicitly seeded generator
  • Create randomly seeded generator (default behavior)

And that these functions would use a cryptographically-secure algorithm and optimized for precision to the extent rand is right now (i.e. use exclusion zones for integer generation, but just 53 bits of precision for floats)

Most other commonly needed things can be easily and intuitively built on top of those functions.

Possibly more functions than the above should have convenience APIs. But it's always easy to add more over time.

However I don't think the rand crate will ever be the crate that matches this goal. Mainly because I believe the "easy to learn" goal implies "small API" which rand decidedly is not. And adding more helpers only moves it in the other direction. But possibly a separate crate built on rand_core could be such a crate.

Honestly I think fastrand gets the API close enough. But I think it has some unfortunate quirks like not using cryptographically secure generation, and the fact that you can seed the TLS generator feels pretty scary to me.

But I think that the rand crate also fulfills an important but different role of being a one-stop-shop for all randomness needs, including more complex algorithms and a more rich API

@dhardy
Copy link
Member

dhardy commented Dec 1, 2020

But possibly a separate crate built on rand_core could be such a crate.

Interesting direction: adding rand_easy or some such built over rand. I'm okay with that.

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

No branches or pull requests

4 participants