2017-05-02 32 views
1

爲了嘗試重載調用語法,我引入了一個簡單的緩存,可以緩存昂貴的計算結果。我對使用一段語法有點困惑。我將在問題之前逐步介紹代碼。是否可以避免「FnOnce」的無意義定義?

緩存的目的是這樣使用:

fn fib(x: i32) -> i32 { 
    if x < 2 { x } else { fib(x-1) + fib(x-2) } 
} 

fn main() { 
    let mut cfib = Cache::new(fib); 

    // Loop that repeats computation and extracts it from the cache 
    // the second time. 
    for x in 1..200 { 
     let val = 5 * x % 40; 
     println!("fibc({}) = {}", val, cfib(val)); 
    } 
} 

我們首先序言中啓用的功能尚未穩定:

#![feature(fn_traits, unboxed_closures)] 

use std::collections::HashMap; 
use std::hash::Hash; 

我們介紹了緩存的結構與HashMap和函數來計算新的值。

struct Cache<T, R> { 
    cache: HashMap<T, R>, 
    func: fn(T) -> R, 
} 

impl<T, R> Cache<T, R> 
    where T: Eq + Hash + Copy, 
      R: Copy 
{ 
    fn new(func: fn(T) -> R) -> Cache<T, R> { 
     Cache { cache: HashMap::new(), func: func } 
    } 

    fn compute(&mut self, x: T) -> R { 
     let func = self.func; 
     let do_insert = || (func)(x); 
     *self.cache.entry(x).or_insert_with(do_insert) 
    } 
} 

我創建FnMut特徵的實現,因爲緩存需要是可變的。

impl<T, R> FnMut<(T,)> for Cache<T, R> 
    where T: Eq + Hash + Copy, 
      R: Copy 
{ 
    extern "rust-call" fn call_mut(&mut self, args: (T,)) 
     -> Self::Output 
    { 
     let (arg,) = args; 
     self.compute(arg) 
    } 
} 

即使我找到的語法FnMut<(T,)>很奇怪,這是好的,安全的,並傳達的意圖相當明顯。因爲我需要定義函數的返回類型,我還想寫開頭爲:

impl<T, R> FnMut<(T,), Output=R> for Cache<T, R> 
    where T: Eq + Hash + Copy, 
      R: Copy 
{} 

但失敗與錯誤:

error[E0229]: associated type bindings are not allowed here 
    --> src/main.rs:55:24 
    | 
55 | impl<T, R> FnMut<(T,), Output=R> for Cache<T, R> 
    |      ^^^^^^^^ associate type not allowed here 

我不得不實施FnOnce是這樣的:

impl<T, R> FnOnce<(T,)> for Cache<T,R> 
    where T: Eq + Hash + Copy, 
      R: Copy 
{ 
    type Output = R; 

    extern "rust-call" fn call_once(self, _arg: (T,)) 
     -> Self::Output 
    { 
     unimplemented!() 
    } 
} 

這是自call_once種毫無意義將永遠不會被調用,並從Associated Types看起來這應該是可能的。但是,它會因相關類型不允許的錯誤而失敗。

Rust Compiler Error Index提到語法Fn(T) -> R並且還說Fn<(T,), Output=U>應該可以工作,但即使我使用夜晚Rust編譯器,我也無法使其工作。

因爲希望在編譯時儘可能多地捕捉錯誤,所以最好避免在FnOnce中創建「未實現」函數,因爲這會在運行時而不是編譯時失敗。

是否有可能僅實現FnMut並以某種方式提供函數的返回類型?

回答

5

Which is kind of pointless since call_once will never be called

這不是你自己決定的;這取決於調用者。他們可能會決定調用FnOnce上下文中的緩存。

好消息是,有一個完全合理的實施FnOnce - 只是委託給FnMut實現:

impl<T, R> FnOnce<(T,)> for Cache<T,R> 
    where T: Eq + Hash + Copy, 
      R: Copy 
{ 
    type Output = R; 

    extern "rust-call" fn call_once(mut self, arg: (T,)) 
     -> Self::Output 
    { 
     self.call_mut(arg) 
    } 
} 

這是編譯器自動執行這些特質呢;如果合適的話,它也代表FnMutFn

又見

+0

看來工作。謝謝。 –

+0

在這裏,「extern」是否需要「防鏽」? –

+0

@MatthieuM。是的。該特徵定義了一個「extern」rust-call「函數,並且實現必須與特徵定義匹配。更廣泛地說,'rust-call'告訴編譯器'arg'實際上是函數的多個參數,並且執行轉換,以便每個元組值都是一個單獨的參數。據推測,傳遞給函數的單個大元組的行爲與硬件級別的許多單獨值不同。 – Shepmaster

相關問題