Use trait blanket implementation to restrict type trait bounds in Rust?

This is repost from stackoverflow.

In the code below: Rust Playground

  //////////////////////////////////////
 // External code from another crate //
//////////////////////////////////////

trait FooExternal {
    fn foo(&self);
}

fn foo_external(f: impl FooExternal) {
    f.foo();
}

  /////////////////////////////////////
 // My generated library code below //
/////////////////////////////////////

trait FooImpl {
    fn foo_impl(&self);
}

impl<T: FooImpl> FooExternal for T {
    fn foo(&self) {
        println!("foo: boilerplate");
        self.foo_impl();
    }
}

  ////////////////////////
 // My user code below //
////////////////////////

#[derive(Debug, Default)]
struct Foo;

// NB: the compiler will yell if FooImpl is not implemented
// try commenting out the impl below
impl FooImpl for Foo {
    fn foo_impl(&self) {
        println!("foo_impl");
    }
}

fn main() {
    println!("Hello, world!");
    let f = Foo::default();
    foo_external(f);
}

The external code expects user to supply a type that implements FooExternal, so foo_external can work on an instance of the type.

However, when implementing the FooExternal trait for a user defined struct Foo, there is some boilerplate code (e.g. setting up opentelemetry tracing span context for each tonic grpc handler) that is common and error prone and a little complex to require an end user to type out.

So I plan to generate (think: proc macro, but codegen is not the issue here!) the common boilerplate implementation of FooExternal, and want user to only focus on the core app logic, and not worrying about the complex and boring chore of typing the same boilerplate over and over again!

So, instead of having user to implement FooExternal for her type Foo, I want the user to implement a generated trait FooImpl, where in the generated blanket trait implementation of FooExternal::foo, the boilerplate code is emitted, and the control is forwarded to FooImpl::foo_impl.

Here's the nice thing in the user code: FooImpl becomes a requirement for the user type Foo (when using foo_external) - if the user forgets to implement FooImpl for Foo, the compiler would kindly yell at you when calling foo_external(f)!

So it seems that the trait blanket implementation effectively have FooExternal bound on FooImpl - even the compiler error message (when the impl FooImpl for Foo is commented out) says:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `Foo: FooImpl` is not satisfied
  --> src/main.rs:49:18
   |
49 |     foo_external(f);
   |                  ^ the trait `FooImpl` is not implemented for `Foo`
   |
note: required because of the requirements on the impl of `FooExternal` for `Foo`
  --> src/main.rs:23:18
   |
23 | impl<T: FooImpl> FooExternal for T {
   |                  ^^^^^^^^^^^     ^
note: required by a bound in `foo_external`
  --> src/main.rs:11:25
   |
11 | fn foo_external(f: impl FooExternal) {
   |                         ^^^^^^^^^^^ required by this bound in `foo_external`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

So, I'm wondering that is trait blanket implementation ever designed to be used in such a scenario (basically tie FooImpl and FooExternal together, a little like extension trait trait FooExternal: FooImpl, but not quite!), and can I rely on this behavior to define my code generation logic to generate the library code as in the code sample?

Any insights would be greatly appreciated!

It works as intended. Rust doesn't have specialization, and can't have overlapping implementations (for any type there can exist one and only one implementation of a trait).

impl<T: FooImpl> FooExternal for T effectively means "the only way to ever use FooExternal for anything is to have FooImpl implemented". These bounds are hard requirements, not a pick-and-choose for implementations.

1 Like

Thanks for the comments, kornel! As an aside, someone gave a deeper insight on my original stackoverflow question, which I think is worth sharing here as well: this

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.