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!