簡體   English   中英

在Rust中使用接受閉包的方法創建對象安全的特性

[英]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實現不是一個好主意,因為這不是一個好的軟件工程實踐。

主要問題是特征中的getupsert方法接受通用類型參數。 我的第一次嘗試是擺脫這些通用類型參數。

對於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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM