Trait bounds puzzle

Hello,

I'm struggling to get a blanket impl to work on a trait that has tighter bounds on one of the methods. I'm trying to blanket impl the trait on a mutable reference to T, if T itself implements the trait.

Here is a simplified version of the code:

trait TraitOne<T> {}

trait TraitTwo {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>;
}

impl<T> TraitOne<T> for u64 {}
impl TraitTwo for u64 {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>
    { 0 }
}

impl<T, U> TraitOne<T> for &mut U where U: TraitOne<T> {}

impl<U> TraitTwo for &mut U where U: TraitTwo {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>
    {
        (**self).bounded()
    }
}

It won't let me put tighter bounds on that one method, because that now conflicts with the trait definition.

I figured I could bound the whole impl by the tighter bound, but that doesn't work either because the tighter bound has a generic that is only relevant in the context of the one method, so I get an "unconstrained parameter" error.

Is there a way to solve this without modifying the trait definitions?

Thanks for any ideas.

I don’t see a way without lifting T to also be a parameter of TraitTwo. Depending on the specifics of how these traits are used and the types they need to be implemented for, I suppose there might be some dyn Any tricks available; not in the fully-general case you’ve asked about, though.

1 Like

Here’s the best I could come up with. The only change to the trait definitions is an additional method on TraitOne with a default implementation, and it has the restriction that &mut T will only forward T::TraitOne<…> if T also implements TraitTwo:

trait TraitOne<T> {
    fn bounded_impl(&mut self) -> usize where Self:TraitTwo {
        self.bounded::<T>()
    }
}

trait TraitTwo {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>;
}

impl<T, U> TraitOne<T> for &mut U where U: TraitOne<T>+TraitTwo {
    fn bounded_impl(&mut self) -> usize {
       <U as TraitTwo>::bounded::<T>(&mut **self)
    }
}

impl<U> TraitTwo for &mut U where U: TraitTwo {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>
    {
        self.bounded_impl()
    }
}
2 Likes

A little indirection does the job.

Edit: Narrator: "It did not do the job."

Incorrect previous reply
trait Helper<T> {
    fn help_bounded(self) -> usize;
}

impl<T, U> Helper<T> for &mut U where U: TraitTwo + TraitOne<T> {
    fn help_bounded(self) -> usize {
        // This auto-refs and you end up with mutual recursion
        (*self).bounded()
    }
}

impl<U> TraitTwo for &mut U where U: TraitTwo {
    fn bounded<T>(&mut self) -> usize
        where Self: TraitOne<T>
    {
        self.help_bounded()
    }
}

Demonstration of recursion.

4 Likes

Ignore my previous reply, it doesn't do what I thought. It adds an auto-ref and recurses forever.

2 Likes

TL;DR: The OP can't work because downstream can implement traits for &mut LocalType when LocalType does not implement the trait, meaning the blanket implementations are not "if and only if"s.


For more context, something seemed off in having the bound succeed when it was part of a trait implementation where clause, but not when it was part of a method where clause. That is, both the OP and the Helper<T> trait implementation are trying to prove that U: TraitOne<T> given U: TraitTwo and &mut U: TraitOne<T>.

Eventually it occurred to me that downstream could implement TraitOne<T> for &mut LocalType without implementing TraitOne<T> for LocalType because &mut LocalType is considered to be a local type, and you can make use of negative reasoning for coherence with local types -- that is, the implementation is allowed even though your blanket implementation exists, so long as your blanket implementation doesn't apply.

(Similarly you can implement Display for &LocalType despite there being a blanket implementation for shared references in std, so long as you don't also implement Display for LocalType.)

I doubted there was a trait solver bug that big, so at that point I actually tested my playground and found out where I'd gone wrong.

But at any rate, the scenario above means that the compiler error in the OP is correct: you can't conclude that U: TraitOne<T> from &mut U: TraitOne<T>.

2 Likes