英文:
How to avoid conflicting implementation issue in a Rust macro involving invocations for different arities?
问题
我有以下特性,并希望它适用于任何 Fn
。
pub trait CloneableFn<Args, O>: Fn(Args) -> O {
fn clone_box<'a>(&self) -> Box<dyn 'a + CloneableFn<Args, O>>
where
Self: 'a;
}
在尝试了各种方法之后,我现在正在尝试使用宏。
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);
这似乎可以编译,但是如果我取消注释 cloneable_fn!(A);
这一行,就会出现错误:
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)
起初,我以为这是一个括号问题,但是即使我将 cloneable_fn!(A);
注释掉并手动添加一个 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())
}
}
是否有一种解决方案可以适用于任何元数(如果解决方案涉及一些特殊情况的实现也可以)?
英文:
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)?
答案1
得分: 3
问题归结为语法怪癖:(A)
不被解释为一个具有泛型类型A
的一元元组,而是仅仅是A
,因此ClonableFn<A, O>
的实现与其他每个ClonableFn
的实现冲突,因为()
、(A, B)
等都是类型。
这也是为什么你的手动实现失败的原因:你试图编写ClonableFn<A, O>
,这与世界上的其他代码冲突。
你想要的实现——一元元组——需要写成CloneableFn<(A,), O>
。
当然,你可以手动编写它...但是你确实可以通过“修复”你的宏来实现它:根据逗号是放在括号内还是括号外,$()*
(和$()+
)的行为不同:
- 在括号内:在每个元素之后都有一个逗号1。
- 在括号外:在每个元素之后都有一个逗号,但最后一个元素除外。
使用 Playground,并点击“工具 > 展开宏”,我们可以看到这种差异的作用。
你的版本(括号外):
#[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())
}
}
修复版本(括号内):
#[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())
}
}
在(A,)
中的尾随逗号起到了关键作用。
1“等等,你说(A, B)
中没有尾随逗号!你是对的,我感到困惑。严格来说,它是不必要的,但即使cargo expand --ugly
也缺少它,尽管它应该是严格展开的代码,没有重新格式化。一旦我弄清楚原因,我会更新这个答案。
英文:
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:
- Inside: present after each element<sup>1</sup>.
- Outside: present after each element but the last.
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.
<sup>1</sup> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论