2017-09-15 78 views
1

我想創建一個映射一個特徵,定義如下的方法創建鏽對象安全的特質:與接受封閉

pub trait Map<K: Sync, V> { 
    fn put(&mut self, k: K, v: V) -> Option<V>; 
    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U); 
    fn get<Q: ?Sized>(&self, k: &Q) -> Option<V> where K: Borrow<Q>, Q: Eq + Hash + Sync; 
    // other methods ommited for brevity 
} 

現在的問題是,如果我實現了這個特質,例如作爲MyHashMap,然後我不能有一個這樣的表達式:

let map: Box<Map<i32, i32>> = Box::new(MyHashMap::<i32, i32>::new()); 

該錯誤將是:

性狀map::Map不能製成物體

如何解決這個問題?因爲直接開始使用Map實現並不是一個好主意,因爲這不是一個好的軟件工程實踐。

主要問題是得到upsert方法在trait中接受泛型類型參數。我的第一個嘗試是擺脫這些泛型類型參數。

對於得到方法,這是可能的,即使它從共同特徵得到生鏽收藏偏離並使其使用方案較爲有限。下面是結果:

pub trait Map<K: Sync, V> { 
    fn put(&mut self, k: K, v: V) -> Option<V>; 
    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U); 
    fn get(&self, k: &K) -> Option<V>; 
    // other methods ommited for brevity 
} 

不過,我沒有要刪除的泛型類型參數UPSERT過程中的任何想法。

有關如何處理此問題的任何想法?

+3

*因爲直接開始使用Map實現並不是一個好主意,因爲它不是一個好的軟件工程實踐。* =>您需要更新您的工程實踐。在Rust或C++等本地語言中,使用具體實例(和泛型)而不是接口可以使編譯器擠出最後一盎司的性能。 –

回答

3

如何解決這個問題?因爲直接開始使用Map實現並不是一個好主意,因爲這不是一個好的軟件工程實踐。

這是Java中的良好習慣,但不一定在其他語言中。例如,在動態類型語言中,如果所有Map實現對這些方法使用相同的命名約定,則可以在不進行大量代碼更改的情況下替換它們。

在類似Rust的語言中,它有很好的類型推斷,通常不需要用過多的類型註釋來污染代碼。因此,如果您需要更改具體類型,則需要更新的地方更少,並且這比在Java等語言中找到的問題要少。

「好」Java有一個隱含的目標,您可能想要在運行時之間交換抽象類型的任何實現。 Java使得這很容易做到,所以這樣做是合理的,儘管在實踐中這很少需要。更有可能的是,您將使用一些需要抽象類型的代碼,並且您可以在編譯時處提供一個具體實例,該編譯器在處已知。

這正是Rust如何使用參數。當您指定參數M: Map時,可以使用任何M,它們也實現Map。但編譯器會在編譯時找出您實際使用的具體實現(這稱爲單態化)。如果您需要更改具體的實現,只需更改一行代碼即可。這對性能也有很大的好處。

所以,回到你的第一個問題:

如何才能解決這個問題呢?

如果你真的想這樣做,你可以引入另一特徵對象的映射函數。 trait對象不能擁有自己的泛型參數的原因是因爲編譯器在編譯時無法知道將會出現的大小。因此,只要你的函數參數爲特徵的對象太多,所以這個問題消失:

fn upsert(&self, key: K, value: V, updater: &Fn(&mut V)); 

但我真正的答案是,正如我上面描述,讓事情變得簡單。如果你確實需要這個抽象,那麼它應該在編譯時已知的實例化類型參數完美地工作。在編譯時無法知道具體類型時使用trait對象,例如實現可以在運行時更改的地方。

2

聲明:我發現前提(良好做法)有缺陷,但仍然認爲值得回答的問題。運行時多態性具有它的地位,特別是減少編譯時間。

這是完全有可能創建特質的對象安全版本,它只是需要兩個組件:

  • ,你希望通過運行時多態性的使用不應該有泛型類型參數的方法,
  • 應該通過where Self: Sized子句來保護具有類型參數(且不能通過運行時多態性使用)的方法。

它可以提供這樣的方法這兩種選擇,雖然在魯斯特它需要不同的名稱:

pub trait Map<K: Sync, V> { 
    fn put(&mut self, k: K, v: V) -> Option<V>; 

    fn upsert_erased(&self, key: K, value: V, updater: &Fn(&mut V)); 

    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U) 
     where Self: Sized 
    { 
     self.upsert_erased(key, value, updater); 
    } 
} 

不是我選擇這裏通過upsert_erased提供的upsert一個默認的實現,它減少了具體類型必須實施的方法數量,同時仍然提供在性能保證的情況下實際執行該方法的可能性。