Refining default impl/ pass through trait in default impl?

Hello everyone,

it's probably easier to show example code than to explain.

trait Foo {}
trait Bar {
    fn bar(&self) {
        println!("bar");
    }
}

struct FooStruct<T>(T);

impl<T> Foo for FooStruct<T> {}
impl<T: Bar> Bar for FooStruct<T> {}

trait Baz {
    fn foo(self) -> impl Foo where Self: Sized {
        FooStruct(self)
    }
}

impl Bar for i32 {
    
}
impl Baz for i32 {
    
}

impl<'a> Foo for & 'a str {
    
}
impl<'a> Baz for  & 'a str {
#[allow(refining_impl_trait)]
    fn foo(self) -> Self {
        self
    }
}

fn main() {
    0.foo();
    0.bar();
    "0".foo();
    FooStruct(0).bar();
   // 0.foo().bar();
}

I want a trait with a default method that isn't fixed to a particular concrete return type, and that doesn't require implementing a particular trait, but that will pass through implementations of that trait when they exist. Is there a way to achieve this?
In the example I'm looking for a way to make the commented out line compile without changing the Baz implementations for i32 or & 'a str.

Ways I could imagine it working but that seem not to be allowed include separate signatures for the method as must be implemented by implementing types and for the default method with the default impl being a refining impl (so that it can return FooStruct), an associated type with a default that is the return value of the method (and implementing types being forced to keep them in sync), specialization on whether Self implements Bar, and conditional constraints on the return type (something like impl Foo + for<Self: Bar> Bar).

Is there some reasonable workaround that does work?

Would making Foo a subtrait of Bar work for you? Example.

No, that wouldn't work. The actual use case is closer to Bar being a subtrait of Foo.

I don't think there's a way. See here to read about how arbitrary additional -> impl Trait bounds are not supported. We'll probably get some way to name return types to work around this eventually.

Just to be sure, you're aware this works, but you consider it unreasonable because the Baz implementation for i32 had to change, right?

impl Baz for i32 {
    #[allow(refining_impl_trait)]
    fn foo(self) -> impl Foo + Bar where Self: Sized {
        FooStruct(self)
    }
}

I am aware, but in the real use case there are several such default methods and many types implementing the trait and relying on the default methods.

I did find a partial workaround myself, but that only works well for either using all the default implementations or none of them, and if the trait does not also have non-default methods.

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.