2017-06-18 103 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'特性無關。 –

相關問題