Requiring evaluation to succeed in the where clause

Hello,

I currently try to make a crate that tracks the range an integer can be after performing operations with it. The intention is to know at compile time if an overflow can happen. I want to include this information in the type definition.

I talked with T-Dark on the Rust Discord about it who also gave me the general idea of doing it like that. However, as this is not possible yet, the user was sure a feature like that intended to land some day but there is also currently no issue about it.

Example code:

struct BoundedU32<const MIN: u32, const MAX: u32>(u32);

impl<const LMIN: u32, const LMAX: u32, const RMIN: u32, const RMAX: u32>
Add<BoundedU32<RMIN, RMAX>> for BoundedU32<LMIN, LMAX>
where
    Evaluates<{ LMIN + RMIN }>, //evaluates if it doesn't overflow
    Evaluates<{ LMAX + RMAX }>,
{
    type Output = BoundedU32<{LMIN + RMIN}, {LMAX + RMAX}>;
    fn add(self, rhs: BoundedU32<RMIN, RMAX>) -> Self::Output {
        BoundedU32(self.0 + rhs.0)
    }
}

Has someone more information about this feature I can track?

Thank you in advance.

This is what I came up with:

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

#[derive(Debug, Copy, Clone)]
struct BoundedU32<const MIN: u32, const MAX: u32>(u32);

impl<const LMIN: u32, const LMAX: u32, const RMIN: u32, const RMAX: u32> Add<BoundedU32<RMIN, RMAX>>
    for BoundedU32<LMIN, LMAX>
where
    [(); { LMIN + RMIN } as usize]: ,
    [(); { LMAX + RMAX } as usize]: ,
{
    type Output = BoundedU32<{ LMIN + RMIN }, { LMAX + RMAX }>;
    fn add(self, rhs: BoundedU32<RMIN, RMAX>) -> Self::Output {
        BoundedU32(self.0 + rhs.0)
    }
}

(playground)

That playground snippet also uses std::any::type_name() to show what happens to your bounds during addition.

fn main() {
    let a = new_bounded::<128>();
    let b = BoundedU32::<0, 64>::new(45);

    println!(
        "{} + {} = {}",
        a.type_name(),
        b.type_name(),
        (a + b).type_name()
    );

    // Error: attempt to compute `64_u32 + u32::MAX`, which would overflow
    // let c = b + new_bounded::<{u32::max_value()}>();
}

Outputs:

playground::BoundedU32<128, 128> + playground::BoundedU32<0, 64> = playground::BoundedU32<128, 192>
1 Like

Thanks, that is a good workaround! However, this solution is limited to u8, u16, u32 and u64 (if compiled on 64bit machines), right? Though I can include the unsigned variants with an offset.

I can work with that for now, still I'd be interested if a planned feature without the workaround is out there to track.

You might be interested in this post:

The relevant feature in your case is generic_const_exprs, which sounds like it's going to be incomplete for a while yet. You can follow progress on implementing this and other const generics features at the working group repository:

1 Like