4

I needed to choose a random value from an enum. In some article about Nim I found this solution:

import random

type Animal = enum
  Cat
  Dog
  Cow

echo rand(0..2).Animal

But this doesn't scale well: If some values are added to or removed from the enum, we have to adjust the upper number.

We can even get a runtime error:

import random

type Animal = enum
  Cat
  Dog

randomize(123)

while true:
  echo rand(0..2).Animal
Cat
Cat
Dog
…/example.nim(10) example
…/.choosenim/toolchains/nim-1.4.4/lib/system/fatal.nim(49) sysFatal
Error: unhandled exception: value out of range: 2 notin 0 .. 1 [RangeDefect]

I am looking for a simple way to choose a random value from an enum1 that is safe, meaning that if it compiles, it is guaranteed that there will be no RangeDefect or similar runtime error.

I would also be interested to know if there is a compiler setting that generates at least a warning in the above example.

The compiler seems to be capable of this in principle:

Animal(5)

→ Error: 5 can't be converted to Animal

After reading in https://nim-lang.org/docs/random.html about

I thought that one of the following could work, but they don't compile:

rand(Animal)

→ Error: type mismatch: got <type Animal>
rand(range(Animal))

→ Error: type mismatch: got <type Animal> but expected 'range = range (None)'
rand(range[Animal])

→ Error: expected range
rand(Slice[Animal])

→ Error: type mismatch: got <type Slice[example.Animal]>
rand(Slice(Animal))

→ Error: type mismatch: got <type Animal> but expected 'Slice = CompositeTypeClass'

This does work, but I guess it is unnecessarily inefficient, because it needs to allocate and fill a sequence:

import sequtils

echo sample(Animal.toSeq)

1I'm assuming no enums with holes, which I'm aware are another issue.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65

1 Answers1

11

A straightforward solution is to use low and high:

rand(Animal.low..Animal.high)

Using a generic proc allows to write rand(Animal):

import random

type Animal = enum
  Cat
  Dog
  Cow

proc rand(T: typedesc): T =
  rand(T.low..T.high)

randomize(123)

for _ in 1..6:
  echo rand(Animal)

Output:

Cat
Cat
Dog
Cow
Cow
Dog
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • just wanted to add that this won't compile for an enum with holes, addressing your safety concern. here you're using rand(HSlice[Animal]) – shirleyquirk Mar 28 '21 at 19:19