2

Say that I have the following trait:

trait SayHello {
    fn say_hello(self) -> String;
}

Why can I then do this:

fn say_hello_twice(this: impl SayHello) -> String {
    let said_hello = this.say_hello();
    said_hello + " " + &said_hello
}

But not this:

impl impl SayHello {
    fn say_hello_twice(self) -> String {
        let said_hello = self.say_hello();
        said_hello + " " + &said_hello
    }
}

When the latter is seemingly just syntactic sugar for the former?

1
  • The "Why can I then do this"-example does not compile and raises an appropriate error message for the situation. Commented Dec 7, 2024 at 10:39

1 Answer 1

5

impl Trait isn't a type: semantically it means "some concrete type that happens to implement Trait". In argument position, as in your original say_hello_twice function, it is just syntactic sugar for a generic—the desugared equivalent being as follows:

fn say_hello_twice<T: SayHello>(this: T) -> String { ... }

It isn't permitted in Rust, so it doesn't actually have any defined meaning, but what might you have intended by using impl Trait as the nominal type of an implementation block? By analogy to the above, perhaps it would be something like this:

impl<T: SayHello> T { ... }

However, this isn't allowed either: such an implementation would apply to all types that implement SayHello: including any that are defined in downstream crates—and inherent implementations can only be defined in the same crate as the type itself, to prevent name conflicts/ambiguity.

If you want instead to define a method that is available on all implementations of SayHello, you have a few choices:

  1. Define it in the SayHello trait, with a default implementation:

    trait SayHello {
        fn say_hello(self) -> String;
        fn say_hello_twice(self) -> String {
            let said_hello = self.say_hello();
            said_hello.clone() + " " + &said_hello
        }
    }
    

    This has the advantage that it is just a normal, readily-available method on SayHello. However, implementors can choose to override the definition—which may or may not be desirable.

  2. Define an extension trait that is blanket implemented:

    trait SayHelloTwice {
        fn say_hello_twice(self) -> String;
    }
    
    impl<T: SayHello> SayHelloTwice for T {
        fn say_hello_twice(self) -> String {
            let said_hello = self.say_hello();
            said_hello.clone() + " " + &said_hello
        }
    }
    

    This has the advantage that implementations of SayHello cannot provide any other definition of SayHelloTwice::say_hello_twice. However, users must bring SayHelloTwice into scope—which is slightly less ergonomic.

  3. It is possible to use dyn Trait as the nominal type of an inherent implementation block (from within the crate that defines Trait):

    impl dyn SayHello {
        fn say_hello_twice(&self) -> String {
            let said_hello = self.say_hello_borrowed();
            said_hello.clone() + " " + &said_hello
        }
    }
    

    However, users would have to be working with trait objects (&dyn SayHello, Box<dyn SayHello>, etc) which involves runtime indirection and may not be what you are after; moreover, because dyn Trait is unsized, you cannot take self by value (hence using &self and say_hello_borrowed above).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.