29

The spinners crate has an enum with a large selection of possible spinners.

Here's the enum (with all values except the top and bottom 4 skipped):

pub enum Spinners {
    Dots,
    Dots2,
    Dots3,
    Dots4,
    ...
    Shark,
    Dqpb,
    Weather,
    Christmas,
}

A new spinner is easy to create:

extern crate spinners;

use spinners::{Spinner, Spinners};
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let sp = Spinner::new(Spinners::Dots9, "Waiting for 3 seconds".into());
    sleep(Duration::from_secs(3));
    sp.stop();
}

However, I wish to select a spinner at random, and this does not work:

let spinner_enum = rng.choose(Spinners).unwrap_or(&Spinners::Dots9);

Because:

error[E0423]: expected value, found enum `Spinners`

let spinner_enum = rng.choose(Spinners).unwrap_or(&Spinners::Dots9);
                              ^^^^^^^^ not a value

How can I choose an enum value at random, and use that to display a random spinner?

Alexander
  • 9,737
  • 4
  • 53
  • 59

3 Answers3

46

Your own enum

Like most abstractions in Rust, random value generation is powered by traits. Implementing a trait is the same for any particular type, the only difference is exactly what the methods and types of the trait are.

Rand 0.5, 0.6, 0.7, and 0.8

Implement Distribution using your enum as the type parameter. You also need to choose a specific type of distribution; Standard is a good default choice. Then use any of the methods to generate a value, such as rand::random:

use rand::{
    distributions::{Distribution, Standard},
    Rng,
}; // 0.8.0

#[derive(Debug)]
enum Spinner {
    One,
    Two,
    Three,
}

impl Distribution<Spinner> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Spinner {
        // match rng.gen_range(0, 3) { // rand 0.5, 0.6, 0.7
        match rng.gen_range(0..=2) { // rand 0.8
            0 => Spinner::One,
            1 => Spinner::Two,
            _ => Spinner::Three,
        }
    }
}

fn main() {
    let spinner: Spinner = rand::random();
    println!("{:?}", spinner);
}

Rand 0.4

Implement Rand for your enum, then use any of the methods to generate a value, such as Rng::gen:

extern crate rand; // 0.4.2

use rand::{Rand, Rng};

#[derive(Debug)]
enum Spinner {
    One,
    Two,
    Three,
}

impl Rand for Spinner {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        match rng.gen_range(0, 3) {
            0 => Spinner::One,
            1 => Spinner::Two,
            _ => Spinner::Three,
        }
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let spinner: Spinner = rng.gen();
    println!("{:?}", spinner);
}

Derive

The rand_derive crate can remove the need for some of this boilerplate, but does not exist for Rand 0.5.

extern crate rand;
#[macro_use]
extern crate rand_derive;

use rand::Rng;

#[derive(Debug, Rand)]
enum Spinner {
    One,
    Two,
    Three,
}
    
fn main() {
    let mut rng = rand::thread_rng();
    let spinner: Spinner = rng.gen();
    println!("{:?}", spinner);
}

Someone else's enum

Since you don't control the enum, you have to copy something into your code in order to reference it. You could create an array of the enum and choose from that:

use rand::seq::SliceRandom; // 0.8.0

mod another_crate {
    #[derive(Debug)]
    pub enum Spinner {
        One,
        Two,
        Three,
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let spinners = [
        another_crate::Spinner::One,
        another_crate::Spinner::Two,
        another_crate::Spinner::Three,
    ];
    let spinner = spinners.choose(&mut rng).unwrap();
    println!("{:?}", spinner);
}

You could replicate the entire enum locally, implement Rand for that, and then have a method that converts back into the other crates representation.

use rand::{
    distributions::{Distribution, Standard},
    Rng,
}; // 0.8.0

mod another_crate {
    #[derive(Debug)]
    pub enum Spinner {
        One,
        Two,
        Three,
    }
}

enum Spinner {
    One,
    Two,
    Three,
}

impl From<Spinner> for another_crate::Spinner {
    fn from(other: Spinner) -> another_crate::Spinner {
        match other {
            Spinner::One => another_crate::Spinner::One,
            Spinner::Two => another_crate::Spinner::Two,
            Spinner::Three => another_crate::Spinner::Three,
        }
    }
}

impl Distribution<Spinner> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Spinner {
        match rng.gen_range(0..=2) {
            0 => Spinner::One,
            1 => Spinner::Two,
            _ => Spinner::Three,
        }
    }
}

fn main() {
    let spinner = another_crate::Spinner::from(rand::random::<Spinner>());
    println!("{:?}", spinner);
}

You could count the number of spinners and do a match:

use rand::Rng; // 0.8.0

mod another_crate {
    #[derive(Debug)]
    pub enum Spinner {
        One,
        Two,
        Three,
    }
}

fn rando<R: Rng>(mut rng: R) -> another_crate::Spinner {
    match rng.gen_range(0..=2) {
        0 => another_crate::Spinner::One,
        1 => another_crate::Spinner::Two,
        _ => another_crate::Spinner::Three,
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let spinner = rando(&mut rng);
    println!("{:?}", spinner);
}

You can implement a newtype and implement the random generation for that:

use rand::{distributions::Standard, prelude::*}; // 0.8.0

mod another_crate {
    #[derive(Debug)]
    pub enum Spinner {
        One,
        Two,
        Three,
    }
}

struct RandoSpinner(another_crate::Spinner);

impl Distribution<RandoSpinner> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> RandoSpinner {
        RandoSpinner(match rng.gen_range(0..=2) {
            0 => another_crate::Spinner::One,
            1 => another_crate::Spinner::Two,
            _ => another_crate::Spinner::Three,
        })
    }
}

fn main() {
    let RandoSpinner(spinner) = rand::random();
    println!("{:?}", spinner);
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I ended up using the `match`-method and write down all the enums manually. It did not feel optimal, but it worked. Thanks for the suggestions and possible solutions! – Alexander Jan 28 '18 at 21:33
  • @Alexander you can always submit a PR to the original crate to implement `Rand` for the enum, removing the non-optimum-ness and making it so that no one else ever needs to perform that specific workaround. – Shepmaster Jan 28 '18 at 21:35
  • That is true, but is this not a weakness in Rust itself? – Alexander Jan 28 '18 at 22:11
  • @Alexander I would not say that a deliberate design decision is a weakness, no. If you were able to implement `Rand` for `Spinner`, then what would happen when either the rand or spinner crates decided to implement `Rand` for `Spinner`? Either your code would stop compiling or do something different. Neither of those is an acceptable choice in a language which needs stability. – Shepmaster Jan 28 '18 at 22:20
  • 3
    Also found the [strum](https://crates.io/crates/strum) crate which seems to have the potential to alleviate the problem somewhat. – Alexander Jan 29 '18 at 10:14
  • 4
    Unfortunately I think `rand-derive` is deprecated as of `rand` 0.5, [looking at the readme.](https://github.com/rust-lang-nursery/rand/tree/master/rand-derive) – Litty Mar 17 '18 at 21:00
  • 1
    @Shepmaster Reasonable precendence rules could be decided. Other languages allow you to shadow existing functionality and then call super() to access it, if it exists, for example. I personally dislike a lot of the seemingly arbitrary constraints Rust puts in place in the name of "safety", as though there's really no other reasonable solution. – mboratko Jun 15 '18 at 13:24
  • @process91 that's entirely your decision. There are plenty of new and interesting languages as well as improvements to existing languages. If you don't like Rust, don't use it; life is too short for that! However, I'd be surprised if many of the decisions were truly arbitrary. It seems more likely that you might be unaware of the rationale. For example, [RFC 1023](https://github.com/rust-lang/rfcs/blob/master/text/1023-rebalancing-coherence.md) talks about some of the decisions behind it, and I'm sure there are more resources if you care to learn further. – Shepmaster Jun 15 '18 at 13:32
  • @Shepmaster Sorry to express frustration about this, just been slogging away at this relatively simple project for a while now. Do you know of any solution to this issue now that the Rand trait is deprecated? The [error message](https://pastebin.com/YswSNEmg) says that it's been replaced by `distributions::Standard`, but the documentation there doesn't seem to address this issue. Following the error message: **the trait `rand::distributions::Distribution` is not implemented for `rand::distributions::Standard`**, I tried to directly implement this trait but that also failed to compile. – mboratko Jun 15 '18 at 13:48
  • @process91 added example of implementing for rand 0.5 – Shepmaster Jun 15 '18 at 15:54
  • @Shepmaster Thanks, that was really helpful. (Were you able to figure this out from the documentation? Any tips in general for how to figure out what needs to be done in a situation like this? I was reading the source code but didn't see any examples of implementing Distribution like this.) – mboratko Jun 15 '18 at 16:43
  • 1
    @process91 I started at [the crate-level docs](https://docs.rs/rand/0.5.1/rand/), saw the link to [`random`](https://docs.rs/rand/0.5.1/rand/fn.random.html), saw it has a bound of `Standard: Distribution`. I knew that `T` would be our custom type, so I looked at [`Distribution`](https://docs.rs/rand/0.5.1/rand/distributions/trait.Distribution.html). It only has [one required method](https://docs.rs/rand/0.5.1/rand/distributions/trait.Distribution.html#tymethod.sample), so I implemented that. It can only use methods of [`Rng`](https://docs.rs/rand/0.5.1/rand/trait.Rng.html), like `gen_range` – Shepmaster Jun 15 '18 at 16:54
  • For anyone looking for something similar to what `rand-derive` did before it was depreciated, I made a [simple macro](https://gist.github.com/ImTheSquid/f1457863e0b63a25f6947957c147b5cb) for unit enums (ones without fields) that does pretty much the same thing for the latest version of `rand` – ImTheSquid Mar 05 '23 at 16:23
4

Since Shepmaster asked I can suggest another couple of options.

Unfortunately rng.choose(Spinners) cannot work because there is no way to iterate over enum values; see: In Rust, is there a way to iterate through the values of an enum?

You could presumably use strum's EnumIter to allow iteration. In Rand 0.4 and 0.5, choose does not support iterators, but you could either collect all options into a Vec or enumerate and match the index. In Rand 0.6, there will be a variant of choose supporting iterators, although it may quite slow (depending on whether we can optimise it for ExactSizeIterators).

use rand::prelude::*;

#[derive(EnumIter)]
enum Spinner { ... }

let mut rng = thread_rng();

let options = Spinner::iter().collect::<Vec<_>>();
let choice = rng.choose(&options);

// or:
let index = rng.gen_range(0, MAX);
let choice = Spinner::iter().enumerate().filter(|(i, _)| i == index).map(|(_, x)| x).next().unwrap();

// with Rand 0.6, though this may be slow:
let choice = Spinner::iter().choose(&mut rng);
// collecting may be faster; in Rand 0.6 this becomes:
let choice = Spinner::iter().collect::<Vec<_>>().choose(&mut rng);

Another option is to use num's FromPrimitive trait with num-derive:

#[derive(FromPrimitive)]
enum Spinner { ... }

let choice = Spinner::from_u32(rng.gen_range(0, MAX)).unwrap();
dhardy
  • 11,175
  • 7
  • 38
  • 46
  • *though this may be slow* — why would it be slower than `rand::seq::sample_iter` "specialized" for `Option` instead of `Vec`? – Shepmaster Jun 15 '18 at 16:57
  • Whoops, I forgot about `sample_iter` (we're completely redesigning this API for 0.6). Yes, it should be similar in performance to `IteratorRandom::choose` in 0.6, but both will be much slower than the other methods for large enums. Except that we [might be able to optimise this](https://github.com/rust-lang-nursery/rand/issues/511) since in examples like this the iterator length is known. – dhardy Jun 15 '18 at 18:19
  • 1
    Why is iteration a requirement for choosing a random element? In the context of maths and programming in general: If you know the length of the enum, picking out a random element should not require any iteration. – Alexander Jun 17 '18 at 07:13
2

Here in the future, we would probably use strum_macros::FromRepr and EnumCount: https://docs.rs/strum_macros/0.24.0/strum_macros/derive.FromRepr.html

use strum::EnumCount;
use strum_macros::{EnumCount, FromRepr};

#[derive(FromRepr, Debug, PartialEq, EnumCount)]
enum Spinners{ Dots, Dots2, ... }

let randomInteger = 0;  //Worst RNG ever!
assert_eq!(Some(Spinners::Dots), Spinners::from_repr(randomInteger));


let spinner_enum =
    Spinners::from_repr(rng.gen_range(0..Spinners::COUNT))
    .unwrap_or(&Spinners::Dots9);

COUNT is created and assigned automatically by EnumCount. from_repr returns None when the number does not correspond to a variant, like maybe it is too high, or you have specified numbers for one or more variants leaving gaps.

Edit: The spinners crate may not work with FromRepr since it requires annotating the enum's definition and you cannot, as far as I know, unless you modify the code; if you do, post a pull request! But if you have defined your own enum, then Bob's your uncle.

David Lotts
  • 550
  • 5
  • 10