如何避免在涉及不同参数数量的 Rust 宏调用中出现冲突的实现问题?

huangapple go评论78阅读模式
英文:

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&lt;Args, O&gt;: Fn(Args) -&gt; O {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;Args, O&gt;&gt;
    where
        Self: &#39;a;
}

After trying various approaches, I'm now trying to use a macro.

macro_rules! cloneable_fn {
    ($($arg:ident),* $ (,)?) =&gt; {
        impl&lt;$($arg,)* O, FN: Fn(($($arg),*)) -&gt; O + Clone&gt; CloneableFn&lt;($($arg),*) , O&gt; for FN
        {
            fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;($($arg),*), O&gt;&gt;
            where
                Self: &#39;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&lt;(), _&gt;`
  --&gt; src/util.rs:52:9
   |
52 |         impl&lt;$($arg,)* O, FN: Fn(($($arg),*)) -&gt; O + Clone&gt; CloneableFn&lt;($($arg),*) , O&gt; 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&lt;A, B, F: Fn(A) -&gt; B + Clone&gt; CloneableFn&lt;A, B&gt; for F {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;A, B&gt;&gt;
    where
        Self: &#39;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&lt;A, O&gt;的实现与其他每个ClonableFn的实现冲突,因为()(A, B)等都是类型。

这也是为什么你的手动实现失败的原因:你试图编写ClonableFn&lt;A, O&gt;,这与世界上的其他代码冲突。

你想要的实现——一元元组——需要写成CloneableFn&lt;(A,), O&gt;

当然,你可以手动编写它...但是你确实可以通过“修复”你的宏来实现它:根据逗号是放在括号内还是括号外,$()*(和$()+)的行为不同:

  • 在括号内:在每个元素之后都有一个逗号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&lt;A, O&gt; conflicts with every other ClonableFn implementation, as (), (A, B), ... are types.

This is also why your manual implementation fails: you attempt to write ClonableFn&lt;A, O&gt; which just conflicts with the world.

The implementation you want -- for unary tuple -- needs to be written for CloneableFn&lt;(A,), O&gt;.

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&lt;O, FN: Fn(()) -&gt; O + Clone&gt; CloneableFn&lt;(), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(), O&gt;&gt; where
        Self: &#39;a {
        Box::new(self.clone())
    }
}
#[allow(unused_parens)]
impl&lt;A, O, FN: Fn((A)) -&gt; O + Clone&gt; CloneableFn&lt;(A), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(A), O&gt;&gt; where
        Self: &#39;a {
        Box::new(self.clone())
    }
}
#[allow(unused_parens)]
impl&lt;A, B, O, FN: Fn((A, B)) -&gt; O + Clone&gt; CloneableFn&lt;(A, B), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(A, B), O&gt;&gt; where
        Self: &#39;a {
        Box::new(self.clone())
    }
}

Fixed version (inside):

#[allow(unused_parens)]
impl&lt;O, FN: Fn(()) -&gt; O + Clone&gt; CloneableFn&lt;(), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(), O&gt;&gt; where
        Self: &#39;a {
        Box::new(self.clone())
    }
}
#[allow(unused_parens)]
impl&lt;A, O, FN: Fn((A,)) -&gt; O + Clone&gt; CloneableFn&lt;(A,), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(A,), O&gt;&gt; where
        Self: &#39;a {
        Box::new(self.clone())
    }
}
#[allow(unused_parens)]
impl&lt;A, B, O, FN: Fn((A, B)) -&gt; O + Clone&gt; CloneableFn&lt;(A, B), O&gt; for FN {
    fn clone_box&lt;&#39;a&gt;(&amp;self) -&gt; Box&lt;dyn &#39;a + CloneableFn&lt;(A, B), O&gt;&gt; where
        Self: &#39;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.

huangapple
  • 本文由 发表于 2023年8月8日 20:05:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76859393.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定