I would like to use it more--I've seen it brought up as a killer feature--but I'm having some trouble understanding the borders of this little kingdom in Nim.
Specifically I don't understand how/when/to what degree `range[a..b]` acts like a "real" type (like int8) and when it doesn't. I also do not have a clear image of when/how/if it is preserved through generics and meta-programming (see below). I don't understand why it isn't a Nim type class like `enum`, even though it almost, kind-of is (ibid). Here are some smaller examples of some explorations I did, trying to learn, but at each step I had trouble finding consistency; each block below has an implicit question of "is this undefined behavior or is it desired/designed to be just-so?" type SmallNat = range[0..9] static: # This erases the `range[a..b]` and happily assigns values outside of the range. proc iwith1(v: var SomeOrdinal, n: int): auto = if n >= int(v.low) and n <= int(v.high): v = typeof(v)(n) var x: SmallNat = 5 x.iwith1(1_000_000_000) assert x is SmallNat and x >= 10 # What does this even mean? static: # This is the same, even though we're allowed to role-play as if `range` is a "type class" so long as we use a sum type. proc iwith2(v: range | void, n: int): typeof(v) = if n >= int(v.low) and n <= int(v.high): typeof(v)(n) else: v const q = SmallNat(1).iwith2(1_000_000_000) assert q isnot SmallNat and q is int and q >= 10 static: # I'm not sure what `range` does or what it is supposed to mean in the sum type, because `range` is # explicitly not a type class. assert not (compiles do: # Error: invalid type: 'range' in this context: 'proc (x: range)' for proc proc rbar(x: range) = discard) static: # Aside: the same confusing drunk-type-class semantics is found with `Ordinal`: assert compiles do: proc ofoo(x: Ordinal | float) = discard ofoo(1) # yup ofoo(littleEndian) # yup ofoo(1.2) # yup assert not (compiles do: # Error: invalid type: 'None' in this context: 'proc (x: Ordinal)' for proc # ^---- much more confusing error message though. proc obar(x: Ordinal) = discard) static: # Despite the aforementioned procs which erase the range, it *is* preserved when we use explicit # generics, even though I had the delusion this function should be semantically equivalent to `iwith1`? proc iwith3[T: SomeOrdinal](v: var T, n: int): auto = static: assert T is SomeOrdinal and T is range if n >= int(v.low) and n <= int(v.high): v = typeof(v)(n) var y: SmallNat = 3 y.iwith3(1_000_000_000) assert y is SmallNat and y == 3 static: # We're also allowed to say `T: range`, again role-playing as a type class, but now it seems # to...work? proc iwith4[Q: range](v: Q, n: int): auto = static: assert Q is range if n >= int(v.low) and n <= int(v.high): typeof(v)(n) else: v assert SmallNat(4).iwith4(1_000_000_000) == 4 assert not compiles(9.iwith4(1_000_000_000)) # it does demand a real, living range[], not an int # Finally, this is actually closer to what I first tried/hoped would work, # assuming A, B would be inferred: proc iwith5[A, B: static[int]](v: range[A..B], n: int): auto = if n >= A and n <= B: range[A..B](n) else: v when not compiles(iwith5(range[1..12](7), 11)): static: echo ":(" # It is however a rather strange function that seems to "trick" compiles(): when (compiles do: assert iwith5[1,12](7, 1100) == 7): static: echo "LIES" # Error: conversion from int literal(7) to range <invalid value>..<invalid value>(int) is invalid # assert iwith5[1,12](7, 1100) == 7 # Also, `range[]` seems to be completely ignored by `static[]': proc stat0(n: static[range[0..5]]): string = when n > 5: return "why" else: return $NimMajor proc stat1[n: static[SmallNat], T](xs: array[n, T]): int = return xs.len proc stat2[n: static[range[0..0]], T](xs: array[n, T]): int = return xs.len proc stat3[n: range[1..4], T](xs: array[n, T]): int = # probably makes no semantic sense, but it compiles? return xs.len static: assert stat0(999) == "why" assert stat1([1,1,1,1,1,1,1,1,1,1,1]) == 11 assert stat2([1,1,1,1,1,1,1,1,1,1,1,0]) == 12 assert stat3([1,1,1,1,1,1,1,1,1,1,1,2,2]) == 13 Run