Auto-implement T<const FLAG: bool> when T<true> and T<false> both implemented

I'm trying this code:

struct T<const FLAG: bool>;

impl T<true> {
    fn foo() -> bool {
        true
    }
}

impl T<false> {
    fn foo() -> bool {
        false
    }
}

fn bar<const FLAG: bool>() -> bool {
    T::<FLAG>::foo()
}

But it fails:

error[E0599]: no function or associated item named `foo` found for struct `T<FLAG>` in the current scope
  --> src/main.rs:57:16
   |
42 | struct T<const FLAG: bool>;
   | -------------------------- function or associated item `foo` not found for this struct
...
57 |     T::<FLAG>::foo()
   |                ^^^ function or associated item not found in `T<FLAG>`
   |
   = note: the function or associated item was found for
           - `T<true>`
           - `T<false>`

I'm wondering does this use case make sense?

My use case is that I want to have a FLAG to control some behavior of T. The flag will never be changed after creation of T, and thus I think using const generics to dispatch at compile time might better than branching on a bool field.

Workaround:

impl<const FLAG: bool> T<FLAG> {
    fn foo() -> bool {
        if FLAG {
            todo!()
        } else {
            todo!()
        }
    }
}
2 Likes

I think your problem is this ad-hoc way of "overloading" a method based on T's const generics isn't expressed in a way the type system can see - i.e. traits.

What you could do is use a trait as a way to statically dispatch to the right foo() implementation.

trait Foo {
  fn foo() -> bool;
}

struct T<const FLAG: bool>;

impl Foo for T<true> {
  fn foo() -> bool { true }
}

impl Foo for T<false> {
  fn foo() -> bool { false }
}

fn bar<const FLAG: bool>() -> bool
where 
  T<FLAG>: Foo
{
  T::<FLAG>::foo()
}

Otherwise @tczajka's suggestion of using a normal if-statement should be enough. After monomorphisation, we will generate something like this for T::<true>::foo() and T::<false>::foo():

impl T<true> {
    fn foo() -> bool {
        if true { ... } else { ... }
    }
}

impl T<false> {
    fn foo() -> bool {
        if false { ... } else { ... }
    }
}

When compiling in release mode, the optimiser will almost certainly optimise the branch away. That's how debug_assert!() can add no overhead in release mode.

1 Like

For non-generic constants (e.g.: if $crate::cfg!(debug_assertions)), the dead branch is optimized-out in debug builds too

Note: by "generic constant", I mean a constant that references generic parameters in any way.

1 Like