为什么对实现Fn特征的特征的引用不可调用?

时间:2017-06-18 10:49:47

标签: rust traits

我找到了自己对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)) })
}

它汇编并完全回答了这个问题。但为了让它更漂亮,我为Fn实施了Mu<T>个特征,如下所示:

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`
}

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

2 个答案:

答案 0 :(得分: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<(&'a Mu<T>,)>中的括号和尾随逗号。

总之,这有效:

#![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*特征的调用委托给对方,以避免重复执行。

答案 1 :(得分:0)

辩护

在简化示例中,我试图防御当前的编译器行为。

声明let t3: &T1 = t2;

  

t2(类型为&T2的特征对象)并将其绑定到变量t3,这是对实现T1的东西的特征对象引用。

t2是引用,但指向T2,它不是T1的引用。由于t2是变量而不是表达式,因此编译器无法引入代码来转换变量,除非此处应用了一些自动转换。不幸的是,该语言不允许使用特征的实现来进行自动转换。

另一方面,let t1: &T1 = &t2;

  

计算表达式&t2并将结果绑定到变量t1,这是对实现T1的东西的特征对象引用。

这里的区别是我们现在有一个表达式,所以必须计算它。类型检查器将确保结果是对T1的引用,为此,编译器必须搜索&T2的特征实现。

所以,虽然反直觉,但目前的编译器行为并没有错。我认为要实现指定的用法,我们想做的不是在特征对象之上实现特征,而是实现转换特征(已经在我的研究列表中),因此编译器可以自动应用转换

道德

简而言之,对特征的引用不能自动转换为对不同特征的引用,无论这些特征如何相互实现,除非涉及某些转换特征。

这是因为对特征的引用的表示包括指向v表的指针。由于不同的特征具有不同的v表,因此在不改变表示的情况下将它们用于另一个是无效的。