Search code examples
typesrustconstraints

How do you specify value constraints in Rust?


I'm looking for a way to move type constraints into some kind of wrapper. As an example, in Ada you might see something like this:

type Element is Integer range 100 .. 1000;

Which is defining a new type Element that --- while still an Integer --- is bound to a specific range. There's also mod which will loop back around (super useful).

In Rust, so far I've been checking this manually within all my functions, ie:

if x < 100 || x >= 1000 {
    // throw some kind of error
}

But it would be really nice to instead define a new type that performs this check for me on assignment, similar to how integers can't overflow by default. I know that we don't have inheritance, but maybe there's some kind of trait I can implement?

TL;DR: I'm sure my method isn't best practice, but what's the standard alternative?


Solution

  • But it would be really nice to instead define a new type that performs this check for me on assignment, similar to how integers can't overflow by default.

    This would indeed be best.

    You can define it as a user-type:

    struct BoundedU16<const MIN: u16, const MAX: u16>(u16);
    

    And then define all the methods you expect, starting with new:

    impl<const MIN: u16, const MAX: u16> BoundedU16<MIN, MAX> {
        pub const fn new(value: u16) -> Result<Self, BoundError> {
            if value >= MIN && value <= MAX {
                Ok(Self(value))
            } else {
                Err(BoundError(value, MIN, MAX))
            }
        }
    }
    

    The main drawback is that at the moment you can't have BoundedInteger<T, const MIN: T, const MAX: T>. The work-around is to use a macro to define the multiple Bounded[I|U][8|16|32|64|size].

    Then you can declare type Element = BoundedU16<100, 1000>;.

    Though do note that any Element is just an alias, here, not a new type. If you declare a new type with struct Element(BoundedU16<100, 1000>) then you need to implement (or derive) all the traits again.


    A trait would allow you to add a validate method, but would not allow you to implement Add, Sub, ... which automatically validate. It's a poorer solution.