[英]Create an Object-safe Trait in Rust with a method that accepts a closure
我想使用以下定義為Map創建一個特征:
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());
錯誤將是:
trait
map::Map
無法制作為對象
如何解決這個問題? 因為直接開始使用Map實現不是一個好主意,因為這不是一個好的軟件工程實踐。
主要問題是特征中的get和upsert方法接受通用類型參數。 我的第一次嘗試是擺脫這些通用類型參數。
對於get方法,即使它偏離了rust集合中get的通用簽名,並使其使用場景更加受限制,也是可能的。 結果如下:
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中刪除通用類型參數的方法一無所知 。
關於如何處理此問題的任何想法?
如何解決這個問題? 因為直接開始使用Map實現不是一個好主意,因為這不是一個好的軟件工程實踐。
這是Java的良好做法,但不一定是其他語言。 例如,在動態類型的語言中,如果所有Map
實現均對方法使用相同的命名約定,則無需大量代碼更改即可替換它們。
在具有良好類型推斷功能的Rust之類的語言中,通常不需要使用過多的類型標注來污染代碼。 結果,如果您需要更改具體類型,則需要更新的地方就更少了,這比在Java之類的語言中遇到的問題少。
“良好” Java的隱含目標是,您可能希望在運行時交換抽象類型的任何實現。 Java使此操作易於實現,因此這樣做是合理的,盡管實際上很少需要這樣做。 更可能的是,您將使用一些期望抽象類型的代碼,並為它提供在編譯時已知的具體實例。
這正是Rust使用參數的方式。 指定M: Map
參數時,您可以使用任何也實現Map
M
但是編譯器會在編譯時確定您實際使用的是哪種具體實現(這稱為“同構化”)。 如果您需要更改具體的實現,則只需更改一行代碼即可。 這對於性能也有巨大的好處。
因此,回到您的第一個問題:
如何解決這個問題?
如果確實要執行此操作,則可以為mapper函數引入另一個 trait對象。 特質對象不能具有帶有其自己的通用參數的方法的原因是,因為編譯器在編譯時無法知道將要存放的內容的大小。 因此,也只需將您的函數參數設置為特征對象,這樣就可以解決此問題:
fn upsert(&self, key: K, value: V, updater: &Fn(&mut V));
但是,如上所述,我的真實答案是保持簡單。 如果您確實需要此Map
抽象,則它應與在編譯時知道實例化的類型參數一起很好地工作。 在無法在編譯時知道具體類型的情況下(例如,在運行時可以更改實現的情況下),請使用trait對象。
免責聲明:我認為前提(良好實踐)有誤,但仍然認為該問題值得回答。 運行時多態性占有一席之地,特別是可以減少編譯時間。
創建特征的對象安全版本是完全可能的,它只需要兩個組件:
where Self: Sized
子句進行保護。 可以提供這兩種方法的兩種選擇,盡管在Rust中它要求使用不同的名稱:
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
的默認實現的upsert_erased
,它減少了具體類型必須實現的方法的數量,同時仍然提供了在性能允許的情況下實際實現它的可能性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.