Simple question: Why this where clause on a trait func doesn't need to be replicated on the impl?

Hi,

I think it is a simple question, but I couldn't find it anywhere.
I have a trait with one function including a where clause:

trait Command {
    fn parse(args: &mut VecDeque<Data>) -> Result<Box<dyn Command>, Box<dyn Error>>
    where
        Self: Sized;
    fn run(self: Box<Self>, mem: Mem) -> Result<Data, String>;
}

Why do their implementations work without the where clause at all??

struct Ping;

impl Command for Ping {
    fn parse(_: &mut VecDeque<Data>) -> Result<Box<dyn Command>, Box<dyn Error>> {
        Ok(Box::new(Ping))
    }

    fn run(self: Box<Self>, _: Mem) -> Result<Data, String> {
        Ok(Data::SimpleString("PONG".into()))
    }
}

You can in most cases implement the method in a way that is more general than the trait requires. Here's another, more subtle example:

struct S(String);

impl<'a> From<&'a str> for S {
    // This accepts a `&'_ str` with any lifetime, not just an `&'a str`
    //         vvvv
    fn from(s: &str) -> Self {
        S(s.into())
    }
}

In the example, the implementation isn't actually more general, since Ping: Sized. But you can do this with a type that is not Sized too:

impl Command for str {
    fn parse(args: &mut VecDeque<Data>) -> Result<Box<dyn Command>, Box<dyn Error>> {
        unreachable!()
    }
    fn run(self: Box<Self>, mem: Mem) -> Result<Data, String> {
        todo!()
    }
}

But you can't actually call the method; the trait bounds are checked at the call site. RFC 3245 will allow opting in to "refined" methods,[1] in which case, you could call it.

If you added the bound in this case, it wouldn't compile. But there are other workarounds,[2] and if RFC 2056 gets implemented it will just be allowed in some form.[3]


  1. and you don't need to opt into refined RPIT return values beyond disabling a warning, already ↩︎

  2. that still don't let you call the method ↩︎

  3. still without letting you call the method ↩︎

5 Likes

Awesome reply, thank you very much!!
I'd read all RFCs related to where clauses I could find, and couldn't find any information about this.

So, a trait just establishes some "lower-bound", i.e., the minimum requirements implementors must put in, but if we somehow can make the impls more general, Rust will gladly accept them... Hummm, very interesting. Rust is smart in some unimaginable ways sometimes.

Would you say it is "more correct" to keep the exact where clause on all the implementations, or is it also good (or even better), to keep them simplified like this? I could remove them from several other commands...

I tend to leave them off if it occurs to me I can do so, which is pretty much always with Sized in particular. The only downsides I can think of are

  • It's less obvious to readers who don't know you can do this and haven't memorized the trait
  • Depending on how RFC 3245 goes, maybe someday you'll be opting into more guarantees by not stating the bounds

For an example of that last point, we just recently got a form of refinement as part of RPIT in traits. You get a warning in the example, but if the trait is in the same module, you don't even get that.[1]

In the specific case of Sized though, in addition to being highly unlikely, it's a breaking change to become not Sized anyway. So you're not actually opting into more guarantees.


  1. I only just now found that out. I think I'll file an issue... ↩︎

1 Like

Great, I'll remove them.

Regarding the RPIT in traits, I've tried to put everything into the same module, but it still triggered a warning. Only when I removed the pub from the trait the warning was gone. An issue seems a nice idea.

Thanks again, @quinedot!

1 Like