3
votes

The trait bound in baz is unnecessarily strong for when flag=true, in which case we only require Foo.

I would like to have that baz can accept where T: Foo and only enforce the Bar bound when flag=false.

trait Foo {}
trait Bar: Foo {}

fn foo<T>(t: T) where T: Foo {}
fn bar<T>(t: T) where T: Bar {}

fn baz<T>(t: T, flag: bool) 
where T: Bar 
{
    if flag {
        foo(t);
    } else {
        bar(t);
    }
}

Changing the bound to where T: Foo will of course not compile:

bar(t)

.........^ the trait Bar is not implemented for T

Introducing a new function quux which can be called by !Bar types is probably the solution I'll have to accept. But is there any way both Bar and !Bar types could both access a single function baz?

A solution which involves a runtime panic if flag=false and T: !Bar is acceptable.

1
I can't see how splitting this up in two functions is "breaking the API". If the baz() function already exists with the Foo trait bound, adding a new function with a Bar bound shouldn't break any existing code.Sven Marnach
In fact it exists in the form with the Bar trait bound, I've just pasted the Foo form of the trait bound which doesn't compile.sphere
So you'd like to loosen the trait bound on baz, from the current Bar to the Foo shown in the question (which doesn't compile)? And by "breaking" the API you mean breaking it up into two functions, not breaking as in making a backward-incompatible change?user4815162342
Yes. I'll edit the question so hopefully this becomes clearer.sphere
Thanks for the edit. I think what you're asking for is not possible in current Rust, but will be possible with specialization. Specialization might allow you to define a new trait and implement it to do one thing for T: Foo and another for T: Bar (which is currently not allowed due to overlap). Then baz would trivially call the method from that trait which would resolve to the right thing.user4815162342

1 Answers

3
votes

I believe what you are asking for is not possible in current Rust, as it requires specialization. With specialization as proposed by the RFC, baz could be implemented using a helper trait with a blanket implementation for T: Foo and a specialized implementation for T: Bar.

Sadly, specialization does not appear to be a priority at this time (being superseded by even more important features like const generics), so it will take a while for it to be implemented and stabilized. Still, for the fun of it, here is an implementation based on specialization as found on current nightly:

#![feature(specialization)]

trait Foo {}
trait Bar: Foo {}

fn foo<T: Foo>(_t: T) -> &'static str {
    "foo"
}
fn bar<T: Bar>(_t: T) -> &'static str {
    "bar"
}

trait Dispatch {
    fn dispatch(self, flag: bool) -> &'static str;
}

impl<T: Foo> Dispatch for T {
    default fn dispatch(self, flag: bool) -> &'static str {
        // there is no way to call bar(self) here, so we can only assert the flag is true
        assert!(flag);
        foo(self)
    }
}

impl<T: Bar> Dispatch for T {
    fn dispatch(self, flag: bool) -> &'static str {
        if flag {
            foo(self)
        } else {
            bar(self)
        }
    }
}

fn baz<T: Foo>(t: T, flag: bool) -> &'static str {
    t.dispatch(flag)
}

fn main() {
    struct A;
    impl Foo for A {}
    assert_eq!(baz(A, true), "foo");
    //baz(A, false) panics

    struct B;
    impl Foo for B {}
    impl Bar for B {}
    assert_eq!(baz(B, true), "foo");
    assert_eq!(baz(B, false), "bar");
}

(Compilable code in the playground.)