防止 xy 的一些背景關系:
我想為我的系統建立一個快取。存在由1:1 關系組成IdType的型別對。EndpointType每一個IdType都只指一個EndpointType,反之亦然。該Id特征(請參見下面的代碼)不是必需的,它僅存在于我當前嘗試使其作業的迭代中。
有太多不同型別的端點——通常應用程式只會使用其中的一小部分——來證明為每個端點靜態構建快取并將其保存在記憶體中是合理的。此外,我希望能夠添加新端點,而無需觸及任何與快取或客戶端本身相關的代碼。
trait 物件不是物件安全的,并且Endpoint由于關聯const的 sSized和未使用self編譯時優化的方法,無法使它們成為物件安全的。
我想出了將它們存盤為一種Any型別的想法。現在我喜歡型別安全,所以我嘗試對它進行更多限制。在未能找到令人滿意的解決方案后,我的想法仍然存在幾個問題:
- 我如何使這種方法起作用?
- 有更好的解決方案嗎?
- 這是聲音嗎?為什么?
- 有沒有辦法讓它發出聲音?
- 我可以在不安全的情況下實作這一目標嗎?
鏈接到 rust 游樂場
use std::sync::Mutex;
use std::collections::HashMap;
pub trait Endpoint: Sized {
type Id;
}
pub trait Id {
type Endpoint;
}
pub struct Client {
cache: Mutex<Cache>,
}
impl Client {
fn get<T: Endpoint>(&self, id: T::Id) -> T {
if let Some(result) = self.cache.lock().unwrap().get(id) {
return result;
}
todo!()
}
}
pub struct Cache {
map: HashMap<Box<dyn Id>, Box<dyn Endpoint>>,
}
impl Cache {
fn get<T: Id>(&self, id: T) -> Option<T::Endpoint> {
if let Some(endpoint) = self.map.get(Box::new(id)) {
let endpoint: Box<T::Endpoint> = unsafe { std::mem::transmute(endpoint) };
Some(endpoint)
} else {
None
}
}
}
uj5u.com熱心網友回復:
我強烈建議使用Box<dyn Any>over std::mem::transmute。一個相當常見的模式是有一個HashMap<TypeId, Box<dyn Any>>. TypeId是一種可用于區分其他型別的型別,它Eq和Hashimpls 的作業方式如您所愿:如果型別相同,則型別 ID 相等。所以你可以有大致像這樣的東西:
struct Cache {
map: HashMap<TypeId, Box<dyn Any>>,
}
impl Cache {
fn get<T>(&self) -> Option<&T> {
let type_id = TypeId::of::<T>();
let any = self.map.get(&type_id)?;
any.downcast_ref()
}
}
這可以像一個粗略的查找表,將一個型別與該型別的單個值相關聯。如果有 aBox<dyn Any>與 的型別 id 相關聯T,但指向其他型別的值,則不會得到 UB,只會得到None.
這種原語可用于圍繞它構建更復雜的快取。例如,您可以有一個包裝 this 提供訪問權限的結構,但它只支持 type 的鍵(T, <T as Id>::Endpoint)。
關鍵是使用Any和downcast_*方法避免不安全。
std::mem::transmute是比較危險的不安全函式之一,在很大程度上應該被視為最后的手段。從檔案:
transmute非常不安全。有很多方法可以使用此函式導致未定義的行為。transmute應該是絕對的最后手段
我還要補充一點,這種模式很常見,可能有一個提供型別安全介面的板條箱,快速搜索得到了這個:https ://crates.io/crates/anymap ,雖然我不能代表這個板條箱特別是質量
編輯:
如果您還希望能夠區分相同Id/Endpoint對型別的多個實體,您可以對其進行修改以存盤帶有 type 的鍵的 hashmap (TypeId, u64),其中u64是對原始鍵進行散列的結果(有點像 SQL復合鍵):
struct Cache {
map: HashMap<(TypeId, u64), Box<dyn Any>>,
}
impl Cache {
fn insert<T>(&mut self, id: T::Id, endpoint: T)
where
T: Endpoint 'static,
T::Id: Hash,
{
let type_id = TypeId::of::<T>();
let hash = {
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
hasher.finish()
};
self.map.insert((type_id, hash), Box::new(endpoint));
}
fn get<T>(&self, id: T::Id) -> Option<&T>
where
T: Endpoint 'static,
T::Id: Hash,
{
let type_id = ...;
let hash = ...;
let option_any = self.map.get(&(type_id, hash));
option_any.and_then(|any| any.downcast_ref())
}
}
這使您可以擁有多個FooEndpoints (with FooIds) 以及BarEndpoints (and BarIds),同時避免 transmute/unsafe,所有這些都只需一次地圖查找。希望這次我能更準確地閱讀您的問題;p
P.S. I have no idea if this particular way of obtaining a u64 hash is "good", I've never actually had to do this before (I've only ever used the Hash trait with std::collections::HashMap/HashSet). Might be worth doing some googling to make sure this isn't doing something horrible
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/426093.html
