Generically inherit const-generic trait with specific implementations for each case

I have a type implementing a const-generic trait with different implementations for specific cases. I want to inherit (and extend) the logic for another type and would prefer not to have to manually enumerate all combinations (just two in this example).

trait Hi<const B: bool> {
    fn hi();
}

struct X;
struct Y;

impl Hi<true> for X {
    fn hi() {
        println!("hi")
    }
}

impl Hi<false> for X {
    fn hi() {
        println!("bye")
    }
}

impl<const B: bool> Hi<B> for Y {
    fn hi() {
        <X as Hi<B>>::hi();
    }
}

fn main() {
    <X as Hi<true>>::hi();
    <X as Hi<false>>::hi();
    
    <Y as Hi<true>>::hi();
    <Y as Hi<false>>::hi();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `X: Hi<B>` is not satisfied
  --> src/main.rs:22:10
   |
22 |         <X as Hi<B>>::hi();
   |          ^ the trait `Hi<B>` is not implemented for `X`
   |
   = help: the following other types implement trait `Hi<B>`:
             `X` implements `Hi<false>`
             `X` implements `Hi<true>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (bin "playground") due to 1 previous error

I do not think this will be possible right now but it could be. I wanted to ask if someone knows a workaround, and if this is something the compiler is planned to support in the future.

1 Like

There might be pragmatic workarounds to enable this API design, but it’s depending on your use-case. Perhaps you could share some more details (unless the ideas below are already sufficient).

Your example code of course is trivially worked around, e.g. simply by

impl<const B: bool> Hi<B> for X {
    fn hi() {
        if B {
            println!("hi")
        } else {
            println!("bye")
        }
    }
}

Rust Playground


In other use cases, you could define a helper trait and only do a generic impl with a trait bound/constraint. Again, for your simple toy example, if the goal is only to make this main function work, you could even skip the helper trait and just write a trait bound about X and Hi:

impl<const B: bool> Hi<B> for Y
where
    X: Hi<B>,
{
    fn hi() {
        <X as Hi<B>>::hi();
    }
}

Rust Playground

1 Like

(I didn't realize this topic wasn't on Internals)

This also comes up in my crate, generic-mutability, but in a more annoying way.

There I have a struct, let's call it Hi<const B: bool, T>[1], and I want to allow users to write functions of the following form

fn (Hi<true, T>) -> Hi<true, U>
fn (Hi<false, T>) -> Hi<false, U>

...where to perform the transformation, you need to call methods on Hi that unwrap and then rewrap the Hi into different types, depending on what B is.
And yet still allow these functions to be called with a generic B.

struct T;
struct U;
fn hi_true(hi: Hi<true, T>) { unimplemented!() }
fn hi_false(hi: Hi<false, T>) { unimplemented! () }

fn hi<const B: bool>(hi: Hi<B, T>) -> Hi<B, U> {
    if B {
        // I, the programmer know that B is true here, but I still can't call hi_true!
        unimplemented!();
    } else {
        // I, the programmer know that B is false here, but I still can't call hi_false!
        unimplemented!();
    }
}

My workaround was to create a pair of proof types IsTrue<const B: bool> and IsFalse<const B: bool> which can only be constructed if the generic argument is indeed what it should be, and generic methods on Hi<B, T> that perform the unwrapping and rewrapping using unsafe, but require a proof type to be sound:

fn unwrap_hi<const B: bool, T>(Hi<B, T>, IsTrue<B>) -> UnwrappedTrue<T>
fn unwrap_hi<const B: bool, T>(Hi<B, T>, IsFalse<B>) -> UnwrappedFalse<T>
fn rewrap_hi<const B: bool, T>(UnwrappedTrue<T>, IsTrue<B>) -> Hi<B, T>
fn rewrap_hi<const B: bool, T>(UnwrappedFalse<T>, IsFalse<B>) -> Hi<B, T>

This works, but:

  1. It's inconvenient to use
  2. Requires unsafe (only encapsulated; and my crate has to use unsafe anyway)
  3. It's a pain for the library user, not just the author

I did create convenience macros that make it somewhat better, but they don't work in all cases and they are hard to generalize to a different, (userland) struct Hi2.


  1. It's GenRef<'s, M: Mutability, T>, where M is functionally equivalent to a const bool ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.