#[derive(Debug, Clone)]
pub struct Schema {
name: String,
sections: Vec<String>,
}
pub struct SchemaRegistry(HashMap<String, Schema>);
impl SchemaRegistry {
pub fn new() -> Self {
SchemaRegistry(HashMap::new())
}
pub fn register<'a>(&mut self, schema: Schema) -> &mut Self {
self.0.insert(schema.name.clone(), schema);
self
}
}
1
共 8 条评论, 1 页
评论区
写评论因为Schema具有name: String的所有权,而插入map会导致schema发生移动,所以& schema.name直接引用是不行的。这时候就要考虑作为K的类型的特点了,String虽然可以被移动,但是其内部有固定不变的数据位置。所以,可以用点魔法,做点unsafe的东西,安全性需要设计者自行考虑:
use std::hash::Hash; use std::{ptr::slice_from_raw_parts, str}; use std::{collections::HashMap, mem::ManuallyDrop}; fn main() { } #[derive(Debug)] pub struct Schema { name: String, _sections: Vec<String>, } pub struct SchemaRegistry<S: Sharing>(HashMap<S, Schema>); pub trait Sharing { /// 依赖于String内部指针的固定特性,创建一个间接共享数据,不具有原始内容的所有权 unsafe fn from_shared(s: &String) -> Self; } /// 方案一比较直观,借用String内部指针,自己构建一个String对象,但是要屏蔽所有权(drop) #[repr(transparent)] #[derive(Hash, PartialEq, Eq, Debug)] struct SharingString(ManuallyDrop<String>); impl Sharing for SharingString { unsafe fn from_shared(s: &String) -> Self { let shared = ManuallyDrop::new(String::from_raw_parts(s.as_ptr() as *mut u8, s.len(), s.capacity())); SharingString(shared) } } /// 方案二不考虑所有权,同样的,构造间接&str引用,生命周期作为unsafe的保证,可以直接上'static #[repr(transparent)] #[derive(Hash, PartialEq, Eq, Debug)] struct SharingStr(&'static str); impl Sharing for SharingStr { unsafe fn from_shared(s: &String) -> Self { SharingStr(str::from_utf8_unchecked(&*slice_from_raw_parts(s.as_ptr(), s.len()))) } } impl<S: Sharing + Hash + Eq> SchemaRegistry<S> { pub fn new() -> Self { SchemaRegistry(HashMap::new()) } pub fn register(&mut self, schema: Schema) -> &mut Self { // self.0.insert(&schema.name, schema); let sharing = unsafe { S::from_shared(&schema.name) }; { // SAFETY: 由于Sharing是共享间接数据,K固定依赖于V存活,所以修改V一定要先修改K,K-V成对处理 self.0.remove_entry(&sharing); self.0.insert(sharing, schema); } self } } #[cfg(test)] mod tests { use crate::*; #[test] fn example() { // let mut registry = SchemaRegistry::<SharingString>::new(); let mut registry = SchemaRegistry::<SharingStr>::new(); registry.register(Schema {name: "abc".to_owned(), _sections: vec!["first".to_owned()]}); println!("{:?}", registry.0); registry.register(Schema {name: "ABC".to_owned(), _sections: vec!["second".to_owned()]}); println!("{:?}", registry.0); registry.register(Schema {name: "abc".to_owned(), _sections: vec!["third".to_owned()]}); println!("{:?}", registry.0); } }
为了展示两种实现,做了个
Sharing
特征来方便复用,SharingString
、SharingStr
是给前面说的方案套了个壳(wrapper)。 具体的实现,可以根据你的功能需求,做一些取舍裁剪,比如上面我在register里面做了通用判断(remove),可以按实际业务优化一下那么一丢丢的少执行点无效代码。最后,如果K不是String类型的,其实也是类似的,就像Rc这种,外部壳的移动不会涉及实质的数据复制,从而达到减少clone的目的
#[derive(Debug, Clone)] pub struct Schema { // name: String, sections: Vec<String>, } pub struct SchemaRegistry(HashMap<String, Schema>); impl SchemaRegistry { pub fn new() -> Self { SchemaRegistry(HashMap::new()) } // pub fn register<'a>(&mut self, schema: Schema) -> &mut Self { pub fn register<'a>(&mut self, name: String, schema: Schema) -> &mut Self { // self.0.insert(schema.name.clone(), schema); self.0.insert(name, schema); self } }
代码调整一下,也许可行。
faststr了解一下
直接Arc糊上
我觉得这帖子里的 internment的方案还不错啊
--
👇
TinusgragLin: 太巧了,上个星期 Rust 用户论坛站给我发的一周汇总中就有类似的问题,楼主可以参考一下。
我倒觉得不是最好的方案,(原帖里也有讨论到,)如果有碰撞就麻烦了。
省流,算hash
pub struct SchemaRegistry(HashMap<u64, Schema>); impl SchemaRegistry { pub fn new() -> Self { SchemaRegistry(HashMap::new()) } pub fn register<'a>(&mut self, schema: Schema) -> &mut Self { use std::hash::BuildHasher; let k=self.0.hasher().hash_one(&schema.name); self.0.insert(k, schema); self } }
--
👇
TinusgragLin: 太巧了,上个星期 Rust 用户论坛站给我发的一周汇总中就有类似的问题,楼主可以参考一下。
太巧了,上个星期 Rust 用户论坛站给我发的一周汇总中就有类似的问题,楼主可以参考一下。