this spits: error TS2322: Type '40' is not assignable to type 'MyLimitedOptionsType'.
but this works: const value: MyLimitedOptionsType = 30;
In Rust I know I can define a const array of my options: const LIMITED_OPTIONS: [usize; 3] = [10, 20, 30];
And then do a potentially runtime check to see if everything is ok:
if LIMITED_OPTIONS.contains(&value) {
// Validated...
}
What I want though is to have this check done automatically, in compile time when applicable and ideally based solely on the type system. Is there a nice way of doing this without an enum and without needing to name the variants?
Off the top of my head, what I think you're looking for is refinement types, which are available in the refinement crate, and hopefully eventually though pattern types.
Note that Rust has not even subrange type, e.g. to define a type WeekDay with only allowed values 1 to 7. I was missing this when I started with Rust, as some other languages like Ada or Nim (I think even Pascal) supports that. I think the reason Rust does not have it, is that checking for valid subranges is expensive at runtime. And it is often recommended to use enums instead. Note that Rusts enums can have custom integer values.
You don't have to make the inner field public. If the field is private (see the NonZero example from above) consumers have to rely on some sort of construction mechanism (e.g. a new associated function or some logic during deserialisation) you provide, allowing you to check the necessary conditions of the inner value during that operation.
I was unaware of both refinement crate and pattern types development. Thanks for pointing out.
One disadvantage of NewType solution is it's quite verbose and can clutter the codebase quickly if we have a multitude of similar limited types. I sketched a generic NewType that can support multiple different inner types and also owns it's options. It seems to solve the problem fine. If anyone is interested here is the playground code:
What do you think, do we really need a vector to store the options, or would an fixed size array work as well, perhaps using const generics? Personally, I would always avoid the indirection and overhead of vectors compared to fixed size arrays. I assume, for many possible values a (hash) set might be an alternative?
spits the error: the type of const parameters must not depend on other generic parameters
HashSet given it's much faster for lookups compared to Vec which is linear in it's lookups can make a difference if we have a huge collection of options. Might be slower to allocate though, I'm not sure. In any case for the limited purposes of LimitedOptions, I think Vec is a better choice because it's simpler.
They can, especially if you use the strum crate (which makes converting to/from your enum easier). They just can't use the built-in discriminant that you can use to make variants correspond to integers.
If you really need to see only the number in the variant name, you could use something like this, though the clarity and idiomaticity are probably debatable:
#[repr(u16)]
enum Val {
_1 = 1,
_2,
_100 = 100
}
#[test]
fn test() {
let x = Val::_2;
assert_eq!(x as u16, 2);
}
There are a number of crates to do the opposite and convert from integer to enum value, if you need that. Some are mentioned above (I believe strum generates a constant function, so you can use constant numbers, but it's still version 0.x and thus implicitly unstable—to check).
It looks like you don't really care about any explicit casting happening at runtime, though.
Short answer: nope. Rust has a clear distinction in between values (the stuff stored in memory), from types (the stuff used to describe as to where + what + how of the first stuff is meant to be initialized + accessed + changed + dropped). The values get thrown around all over during your program's execution - from CPU to RAM to SSD to you name it. The types get erased during the compilation process itself, only serving as a backbone to validate your code - as you're writing it.
To rustc, a declaration of type T = 0 | 1 | 2 is thus: a complete and utter nonsense.
The closest thing to your example would be:
const fn limit(i: i32) -> i32 {
if let 10 | 20 | 30 = i { return i; }
else { panic!("invalid `i` provided") };
}
// compile-time check, rust-analyzer friendly
const L: i32 = limit(40);
// compile-time error, still
let l = const { limit(50) };
// run-time panic
let _ = limit(60);
Not sure if there's any lint that would warn against calling a const fn at runtime when it could be called in a const setting, instead. Maybe other guys can point out a bit more on that.
That's not actually true. According to Rust's flavor of SemVer, in 0.x.y, x is the major version and y is the minor. In other words, when the first number is 0, bumping the second number is a breaking change. A number of popular crates (like strum and log), that are very much stable, are still on 0.x.y releases.
Can you point us to the legal document please. I learned your definition from various Rust resources, and so was a bit confused when they created the issue Chapter 2. Improvement about SemVer · Issue #4352 · rust-lang/book · GitHub some days ago, with no one disagreeing. Their provided link for item 4 Semantic Versioning 2.0.0 | Semantic Versioning mentions indeed instability of 0.x.y versions, so I first thought I missed this fact. I have not yet found in the Cargo book the precise legal definition.
This guide uses the terms “major” and “minor” assuming this relates to a “1.0.0” release or later. Initial development releases starting with “0.y.z” can treat changes in “y” as a major release, and “z” as a minor release. “0.0.z” releases are always major changes. This is because Cargo uses the convention that only changes in the left-most non-zero component are considered incompatible.
While it's not very explicit, the consensus I've found is that this results in the situation I described. While that's not necessarily the best state of affairs (0.x.y as "unstable, do not use" is handy, and still applies to many crates), given the number of crates that don't abide by the official SemVer standard, it's too late to turn back.