2017-06-18 100 views
1

我发现我自己的答案my own question为以下为什么对实现Fn特征的特征的引用不可调用?

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

是我们努力实现的一个特征对象引用一个特点,那么我们就需要添加额外的基准转换性状物体进入的一个特征对象时的问题目标特质。

+0

请检查[MCVE]是什么,为什么它很重要,以及如何创建一个。例如,您的整个示例可以[详细说明](https://play.integer32.com/?gist=00b2e53268e0527e43aade845148195b&version=nightly)。 – Shepmaster

+0

虽然它不是最小的,但我的例子是完整的和可验证的。之所以这么做并不轻微,是因为我昨天在睡觉前刚刚发现了这个可靠的例子,并且不想在睡前过多的时间,但仍然希望让别人看到它。 –

回答

0

国防

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

声明let t3: &T1 = t2;

采取t2(这是&T2类型的性状对象)并将其绑定到变量t3,这是一个特征的对象引用的东西实现T1

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

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

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

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

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

道德

总之,一个性状的引用不能自动转换为参照不同的特质,不论这些特质如何被实施了对方,除非参与一些转换特征。

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

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))) 
    |     ^^^^^^^ 

那是因为原来执行的th e 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*性状相互调用,以避免执行重复。

+0

感谢您的详细和有益的答案。我注意到你在类型约束上使用了不同的样式(我使用内联样式,你使用where子句)。你还可以简单解释一下为什么你认为这样更好? –

+0

@EarthEngine我发现where子句更具可读性,更易于扩展,再加上它们稍微强大一些。类型和生命周期参数通常非常短(单个字母),并且很多都一起挤在一起。 Where子句添加了一些空白,清楚地标识出有约束,它们更容易剔除(我可以检查每一行以查看它是否相关),当我需要添加另一个时,我可以更改一行而无需额外差异。 – Shepmaster

+0

我更新了问题以进一步简化示例。这与封闭和'FnXXX'特性无关。 –

相关问题