Netzdoktor
Netzdoktor

Reputation: 526

Checking Constant Generics for Certain Properties in Rust

In this playground, I want to implement a method only for const generic parameters for which a certain property holds: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=3e4d5f9f27912d032308a390a56f5f94

I am using a zero-sized type and add a method to it:

pub struct Resource<const N: usize> {}

impl<const N: usize> Resource<N> {
    const fn shorten<const M: usize>(self) -> Resource<M>
    where
        [(); N - M]:, // type existence only ensured if N >= M
    {
        // Runtime checks, though they should not be needed
        if M <= N {
            Resource {}
        } else {
            panic!("Resources can only be shortened")
        }
    }
}

The idea is that a Resource<N> type can be shortened to Resource<M> if N >= M.

However, when I use it like this:

pub fn bar<const N: usize>(
    resource: Resource<N>,
) -> Result<((), Resource<{ N - 1 }>), Box<dyn std::error::Error>>
where
    [(); N - 1]:,
{
    Ok(((), resource.shorten::<{ N - 1 }>()))
}

I get the following compiler error:

error: unconstrained generic constant
  --> src/main.rs:43:22
   |
43 |     Ok(((), resource.shorten::<{ N - 1 }>()))
   |                      ^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); N - M]:`
note: required by a bound in `Resource::<N>::shorten`
  --> src/main.rs:8:14
   |
6  |     const fn shorten<const M: usize>(self) -> Resource<M>
   |              ------- required by a bound in this
7  |     where
8  |         [(); N - M]:, // type existence only ensured if N >= M
   |              ^^^^^ required by this bound in `Resource::<N>::shorten`

I understand (and found in other online resources) that the compiler suggestion with where might be misleading (as it is not a commonly used feature). Ignoring the suggestion, I am not sure why and where this bound is required in the first place.

In bar, the shorten call is executed on Resource<N> (clear from parameter) to return Resource<{N - 1}> (clear from turbo-fish). What am I missing?

Happy to hear some thoughts from more experienced Rustaceans.

Upvotes: 0

Views: 299

Answers (2)

Netzdoktor
Netzdoktor

Reputation: 526

I was told by a colleague that liquid types (as in Haskell) would allow such computation on the types vs. verbatim replacement as explained in the accepted answer.

The Flux project (and publication) seem to address this, but that is still early days.

Upvotes: 0

Chayim Friedman
Chayim Friedman

Reputation: 71585

The compiler does not analyze the expressions (in general this is also impossible). It does verbatim replacement. If you want this code to compile, you need to replace N and M with the actual value used:

pub fn foo<const N: usize>(
    resource: Resource<N>,
) -> Result<((), Resource<{ N - 2 }>), Box<dyn std::error::Error>>
where
    [(); N - (N - 1)]:,
    [(); (N - 1) - (N - 2)]:,
{
    let (baz, resource): ((), Resource<{ N - 1 }>) = bar::<{ N }>(resource)?;
    resource.dbg();
    // new resource is N-1; returning it as N-2 should work, as it is smaller
    // 'shorten<M>'  does not exist, if there is no conversion possible

    Ok((baz, resource.shorten::<{ N - 2 }>()))
}

pub fn bar<const N: usize>(
    resource: Resource<N>,
) -> Result<((), Resource<{ N - 1 }>), Box<dyn std::error::Error>>
where
    [(); N - (N - 1)]:,
{
    Ok(((), resource.shorten::<{ N - 1 }>()))
}

Playground.

Upvotes: 1

Related Questions