'How do I choose a random value from an enum?

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?



Solution 1:[1]

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();

Solution 2:[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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2