【问题标题】:Why is a reference to a trait implementing the Fn traits not callable?为什么对实现 Fn 特征的特征的引用不可调用?
【发布时间】:2017-06-18 10:49:46
【问题描述】:

我找到了自己对my own question 的回答,如下所示

trait Mu<T> {
    fn unroll(&self, &Mu<T>) -> T;
}

impl<T, F:Fn(&Mu<T>) -> T> Mu<T> for F {
    fn unroll(&self, o:&Mu<T>) -> T { self(o) }
}

fn y<T, F:Fn(T) -> T>(f:&F) -> T {
    (&|w:&Mu<T>| { w.unroll(w) }).unroll(&|w:&Mu<T>| { f(w.unroll(w)) })
}

它编译并完全回答了问题。但为了让它更漂亮,我为Mu&lt;T&gt; 实现了Fn 特征,如下所示:

impl<'a, T> Fn<&'a Mu<T>> for &'a Mu<T> {
    extern "rust-call" fn call(&self, o: &'a Mu<T>) -> T {
        self.unroll(o)
    }
}

impl<'a, T> FnMut<&'a Mu<T>> for &'a Mu<T> {
    extern "rust-call" fn call_mut(&mut self, o: &'a Mu<T>) -> T {
        self.unroll(o)
    }
}

impl<'a, T> FnOnce<&'a Mu<T>> for &'a Mu<T> {
    type Output = T;
    extern "rust-call" fn call_once(self, o: &'a Mu<T>) -> T {
        self.unroll(o)
    }
}

有特色

#![feature(fn_traits)]
#![feature(unboxed_closures)]

我想把 y 组合器写成

fn y1<T, F:Fn(T) -> T>(f:&F) -> T {
    (&|w:&Mu<T>| { w(w) })(&|w:&Mu<T>| { f(w(w)) })
}

但这不会编译。错误信息:

rustc 1.19.0-nightly (78d8416ca 2017-06-17)
error[E0618]: expected function, found `&Mu<T>`
  --> <anon>:36:20
   |
36 |     (&|w:&Mu<T>| { w(w) })(&|w:&Mu<T>| { f(w(w)) })
   |                    ^^^^
   |
note: defined here
  --> <anon>:36:8
   |
36 |     (&|w:&Mu<T>| { w(w) })(&|w:&Mu<T>| { f(w(w)) })
   |        ^

error[E0618]: expected function, found `&Mu<T>`
  --> <anon>:36:44
   |
36 |     (&|w:&Mu<T>| { w(w) })(&|w:&Mu<T>| { f(w(w)) })
   |                                            ^^^^
   |
note: defined here
  --> <anon>:36:30
   |
36 |     (&|w:&Mu<T>| { w(w) })(&|w:&Mu<T>| { f(w(w)) })
   |                              ^

为什么 Rust 无法找出给定的 Fn 实现?有什么办法可以改善吗?

进一步的尝试表明它与这些功能无关,甚至与闭包有关。即使答案中显示的示例 Shepmaster 也不是最小的。一个最小的示例如下所示:

trait T1 {}

trait T2 {}

impl<'a> T1 for &'a T2 {}

struct S {}

impl T2 for S {}

fn main() {
    let t2: &T2 = &S {};
    let t1: &T1 = &t2; //This is OK
    let t3: &T1 = t2; //E0308: Expecting `T1`, found `T2`
}

问题是我们试图为 trait 对象引用实现一个 trait,然后我们需要在将 trait 对象转换为目标 trait 的 trait 对象时添加额外的引用。

【问题讨论】:

  • 请查看minimal reproducible example 是什么,为什么它很重要,以及如何创建一个。例如,您的整个示例可以是boiled down to this。
  • 虽然不是最小的,但是我的例子是完整且可验证的。之所以不小,是因为我昨天在睡前想出了这个可靠的例子,不想睡前的时间太长,但还是想让别人看到。

标签: rust traits


【解决方案1】:

在与 Rust 开发人员讨论后,我们认为这是某种错误。为此,我们已提交issue 42736。

一个较小的示例显示了与特征分开的问题。它实际上是任何参考,而不仅仅是特征:

#![feature(fn_traits)]
#![feature(unboxed_closures)]

struct S;

fn repro_ref(thing: &S) {
    thing();
}

impl<'a> FnOnce<()> for &'a S {
    type Output = ();

    extern "rust-call" fn call_once(self, _arg: ()) -> () {}
}

fn main() {}

有一个解决方法是采取另一个参考:

fn ok_ref_ref(thing: &S) {
    (&thing)();
}

但这并不能修复原始示例:

fn y1<T, F>(f: &F) -> T
where
    F: Fn(T) -> T,
{
    (&|w: &Mu<T>| (&w)(w))(&|w: &Mu<T>| f((&w)(w)))
}
error[E0059]: cannot use call notation; the first type parameter for the function trait is neither a tuple nor unit
  --> src/main.rs:41:19
   |
41 |     (&|w: &Mu<T>| (&w)(w))(&|w: &Mu<T>| f((&w)(w)))
   |                   ^^^^^^^

这是因为 Fn* 特征的原始实现并不完全正确。参数应该是单个元组。注意Fn&lt;(&amp;'a Mu&lt;T&gt;,)&gt; 中的括号和尾随逗号。

总的来说,这是可行的:

#![feature(fn_traits)]
#![feature(unboxed_closures)]

trait Mu<T> {
    fn unroll(&self, &Mu<T>) -> T;
}

impl<T, F> Mu<T> for F
where
    F: Fn(&Mu<T>) -> T,
{
    fn unroll(&self, o: &Mu<T>) -> T {
        self(o)
    }
}

impl<'a, T> Fn<(&'a Mu<T>,)> for &'a Mu<T> {
    extern "rust-call" fn call(&self, o: (&'a Mu<T>,)) -> T {
        self.unroll(o.0)
    }
}

impl<'a, T> FnMut<(&'a Mu<T>,)> for &'a Mu<T> {
    extern "rust-call" fn call_mut(&mut self, o: (&'a Mu<T>,)) -> T {
        self.call(o)
    }
}

impl<'a, T> FnOnce<(&'a Mu<T>,)> for &'a Mu<T> {
    type Output = T;
    extern "rust-call" fn call_once(mut self, o: (&'a Mu<T>,)) -> T {
        self.call_mut(o)
    }
}

fn y1<T, F>(f: &F) -> T
where
    F: Fn(T) -> T,
{
    (&|w: &Mu<T>| (&w)(w))(&|w: &Mu<T>| f((&w)(w)))
}

fn main() {}

我还将来自 Fn* 特征的调用委托给彼此以避免重复实现。

【讨论】:

  • 感谢您提供详细而有用的答案。我注意到您在类型约束上使用了不同的样式(我使用内联样式,您使用 where 子句)。您能否还包括一个简短的解释为什么您认为这更好?
  • @EarthEngine 我发现 where 子句更具可读性和更易于扩展,而且它们的功能更强大。类型和生命周期参数通常很短(单个字母)并且很多都挤在一起。 where 子句添加了一些空格,清楚地表明存在约束,它们更容易浏览(我可以检查每一行以查看它是否相关),当我需要添加另一个时,我可以更改一行而没有多余的差异。
  • 我更新了问题以进一步简化示例。与闭包和 FnXXX 特征无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-02
  • 1970-01-01
  • 2019-12-08
  • 2021-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多