Skip to content

Const generics do not accept exhaustive trait impls #146962

@clarfonthey

Description

@clarfonthey

I tried this code:

pub struct Test<const FLAG: bool>;
pub trait Supertrait {}
pub trait Subtrait: Supertrait {}

impl Supertrait for Test<true> {}
impl Supertrait for Test<false> {}
impl<const FLAG: bool> Subtrait for Test<FLAG> {}

I expected the compiler to accept the implementation for Subtrait because Supertrait is implemented for every possible value of FLAG.

Instead, it cannot unify these implementations and gives an error:

error[E0277]: the trait bound `Test<FLAG>: Supertrait` is not satisfied
 --> src/lib.rs:7:37
  |
7 | impl<const FLAG: bool> Subtrait for Test<FLAG> {}
  |                                     ^^^^^^^^^^ the trait `Supertrait` is not implemented for `Test<FLAG>`
  |
  = help: the following other types implement trait `Supertrait`:
            Test<false>
            Test<true>
note: required by a bound in `Subtrait`
 --> src/lib.rs:3:21
  |
3 | pub trait Subtrait: Supertrait {}
  |                     ^^^^^^^^^^ required by this bound in `Subtrait`

For more information about this error, try `rustc --explain E0277`.

Meta

rustc --version --verbose:

rustc 1.92.0-nightly (9f32ccf35 2025-09-21)
binary: rustc
commit-hash: 9f32ccf35fb877270bc44a86a126440f04d676d0
commit-date: 2025-09-21
host: x86_64-unknown-linux-gnu
release: 1.92.0-nightly
LLVM version: 21.1.1

Other remarks

This form of "exhaustive unification" is probably undesirable for things like integers (since even 256 trait implementations is quite a lot), but it is reasonable for boolean generics and will likely be desired as well once adt_const_params (tracked in #95174) is stable. In fact, there will probably be some desire to use ADT const generics to replace booleans for readability, so, this case doesn't seem too far-fetched.

Note that it is possible to make these cases much more complicated by adding two, three, or more boolean arguments, and those should be dealt with similarly.

Less-convoluted example

I'm personally interested in using this for the sake of avoiding macros in trait implementations, because it's immediately apparent why this is useful if you add associated types, for example:

pub trait Supertrait {
    type Error: Default;
}
pub trait Subtrait: Supertrait {
    fn do_thing(self) -> Result<Self, <Self as Supertrait>::Error>;
}

(this is still convoluted, but at least helpful to show the justification here)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coherenceArea: CoherenceA-const-genericsArea: const generics (parameters and arguments)A-trait-systemArea: Trait systemC-bugCategory: This is a bug.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions