Search code examples
genericsrusttraitsborrow-checker

Why is my type-parameter in this impl block unconstrained?


Hopefully a straightforward question for the Rust generics wizards. I'm looking to write a Display impl for anything that can be borrowed as my Offset type (e.g. Offset, &Offset, Box<Offset>, etc). And have come up with the following:

impl<O, C> Display for Modification<O>
where
    O: Borrow<Offset<C>>,
    C: Display,

What I believe is causing the issue here is that my Offset struct has a type-parameter, and that parameter needs to implement Display for the overall Modification<O> to do so.

As is, this code generates the error:

error[E0207]: the type parameter `C` is not constrained by the impl trait, self type, or predicates
  --> crates/polychem/src/polymers/modification.rs:42:9
   |
42 | impl<O, C> Display for Modification<O>
   |         ^ unconstrained type parameter

From my perspective, it is very much indeed constrained by the impl predicates, and this works just fine:

impl<O, C> Display for Modification<O>
where
    O: Borrow<C>,
    C: Display,

So something about throwing that generic struct in the mix is either (1) revealing a limitation in Rust's trait-solving, or — much more likely — (2) is indeed unconstrained nonsense, but in a way I don't currently I understand...

Happy to provide more information and context if it's deemed helpful!


Solution

  • It is unconstrained.

    Rust requires that a given trait be implemented for a type at most once. (This is called coherence.) However, this impl block allows for an infinite number of implementations of Display for one instantiation of Modification<O> by varying the C type.

    Because Borrow is generic, it's possible for the concrete type represented by O to implement Borrow<Offset<A>> and Borrow<Offset<B>> for different types A and B. Which one should the impl block choose? There's no clear way to decide, and therefore this impl is disallowed.

    From my perspective, it is very much indeed constrained by the impl predicates, and this works just fine

    No, this also fails:

    error[E0207]: the type parameter `C` is not constrained by the impl trait, self type, or predicates
     --> src/lib.rs:6:9
      |
    6 | impl<O, C> Display for Modification<O>
      |         ^ unconstrained type parameter
    

    One way you could solve this is by creating a trait that uses an associated type instead of a generic to specify the "canonical" offset type:

    trait BorrowOffset: Borrow<Offset<Self::OffsetType>> {
        type OffsetType;
    }
    

    Now you can reference this associated type in the Display implementation for Modification<O> instead of needing an impl generic type:

    impl<O> Display for Modification<O>
    where
        O: BorrowOffset,
        O::OffsetType: Display,
    {
        // ...
    }