use std::{borrow::Cow, hash::Hash};
struct SniMap(HashMap<PeerKey<'static>, Peer>);
impl SniMap {
pub(crate) fn get<'s, 'sni: 's>(&'s self, sni: &'sni str) -> Option<&'s Peer> {
self.0.get(&PeerKey::Exact(Cow::Borrowed(sni))).or_else(|| {
self.0.get(&PeerKey::Subdomain(Cow::Borrowed(
sni.split_once('.').map(|(_, prior)| prior).unwrap_or(sni),
)))
})
}
}
#[derive(Debug, PartialEq, Eq)]
enum PeerKey<'k> {
Exact(Cow<'k, str>),
Subdomain(Cow<'k, str>),
}
impl Hash for PeerKey<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
PeerKey::Exact(s) => s.hash(state),
PeerKey::Subdomain(s) => {
state.write(b"domain:");
s.hash(state)
}
}
}
}
struct Peer {
}
非常平平无奇的代码. 于是有新的需求: 想缓存 get 的结果 (把按 domain:example.com
匹配到的 sub.example.com
当 full:sub.example.com
写入表内), 达到下次读哈希表一次就能把 Peer 读出来.
有这么几个方案:
- 改用三方库
dashmap
提供的 DashMap, 类似 RwLock 但 API 层面 是 Lock-free 的. - 套一层 Mutex / RwLock.
RefCell 毙掉, 我们需要 Send + Sync, 应该用其线程安全版本: RwLock
但事实上都是不可行的, 为什么? 欢迎 Rust 初学者自行查阅资料 (问 AI 也行)~
答案 (实际上编译器已经提示了):
对于具有内部可变性的 Cell 类似物, 子类型型变要求是不变 (invariant) 的. 本例中 DashMap 内部用了 lock_api 库的 RwLock, 这玩意内部用了 UnsafeCell, 故相应字段涉及的泛型类型 K 要求是不变的.
不能返回本地变量 (拿到的 &Peer 只能活得和上锁后拿到的 Guard 一样久). 起码在当前的 API 设计下不能这么干. 当然, 写出来 get_and_map
一类的奇怪 API 也不是不行.
评价: 写 Rust 的福报