I have the following trait, and would like it to work for any Fn
.
pub trait CloneableFn<Args, O>: Fn(Args) -> O {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<Args, O>>
where
Self: 'a;
}
After trying various approaches, I'm now trying to use a macro.
macro_rules! cloneable_fn {
($($arg:ident),* $ (,)?) => {
impl<$($arg,)* O, FN: Fn(($($arg),*)) -> O + Clone> CloneableFn<($($arg),*) , O> for FN
{
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<($($arg),*), O>>
where
Self: 'a,
{
Box::new(self.clone())
}
}
};
}
cloneable_fn!();
// cloneable_fn!(A);
cloneable_fn!(A, B);
cloneable_fn!(A, B, C);
cloneable_fn!(A, B, C, D);
cloneable_fn!(A, B, C, D, E);
cloneable_fn!(A, B, C, D, E, F);
cloneable_fn!(A, B, C, D, E, F, G);
This appears to compile, but if I uncomment the line cloneable_fn!(A);
, there is an error:
error[E0119]: conflicting implementations of trait `util::CloneableFn<(), _>`
--> src/util.rs:52:9
|
52 | impl<$($arg,)* O, FN: Fn(($($arg),*)) -> O + Clone> CloneableFn<($($arg),*) , O> for FN
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation
...
65 | cloneable_fn!(A);
| ---------------- in this macro invocation
|
= note: this error originates in the macro `cloneable_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
At first, I thought it had to do with a parentheses issue, but the issue remained I left cloneable_fn!(A);
commented and manually added an impl:
impl<A, B, F: Fn(A) -> B + Clone> CloneableFn<A, B> for F {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<A, B>>
where
Self: 'a,
{
Box::new(self.clone())
}
}
Is there a solution to this approach that would work with any arity (it's fine if the solution involves some special case implementations)?
The issue boils down to a syntax quirk: (A)
is not interpreted as a 1-ary tuple of a generic type A
, but as just A
, and thus the implementation for ClonableFn<A, O>
conflicts with every other ClonableFn
implementation, as ()
, (A, B)
, ... are types.
This is also why your manual implementation fails: you attempt to write ClonableFn<A, O>
which just conflicts with the world.
The implementation you want -- for unary tuple -- needs to be written for CloneableFn<(A,), O>
.
You could, of course, write it manually... but it can indeed be achieved by "fixing" your macro: depending on whether the comma is placed inside or outside the parentheses, $()*
(and $()+
) behave differently:
Using the Playground, and clicking on "Tools > Expand Macros" we can see the difference in action.
Your version (outside):
#[allow(unused_parens)]
impl<O, FN: Fn(()) -> O + Clone> CloneableFn<(), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(), O>> where
Self: 'a {
Box::new(self.clone())
}
}
#[allow(unused_parens)]
impl<A, O, FN: Fn((A)) -> O + Clone> CloneableFn<(A), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(A), O>> where
Self: 'a {
Box::new(self.clone())
}
}
#[allow(unused_parens)]
impl<A, B, O, FN: Fn((A, B)) -> O + Clone> CloneableFn<(A, B), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(A, B), O>> where
Self: 'a {
Box::new(self.clone())
}
}
Fixed version (inside):
#[allow(unused_parens)]
impl<O, FN: Fn(()) -> O + Clone> CloneableFn<(), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(), O>> where
Self: 'a {
Box::new(self.clone())
}
}
#[allow(unused_parens)]
impl<A, O, FN: Fn((A,)) -> O + Clone> CloneableFn<(A,), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(A,), O>> where
Self: 'a {
Box::new(self.clone())
}
}
#[allow(unused_parens)]
impl<A, B, O, FN: Fn((A, B)) -> O + Clone> CloneableFn<(A, B), O> for FN {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<(A, B), O>> where
Self: 'a {
Box::new(self.clone())
}
}
The trailing comma in (A,)
makes all the difference.
1 Wait, you say, there's no trailing comma in (A, B)
! You are correct, and I am puzzled. Strictly speaking it's unnecessary, but even cargo expand --ugly
lacks it, even though it should be strictly expanded code, with no reformatting. I'll update this answer once I understand why.