我正在尝试使用
HashSet
创建堆分配对象的缓存。我的想法是,我有一个包装字节引用的结构,在创建新对象时,我检查它包装的字节是否已在使用中,如果是,则重新使用现有的堆分配的字节。
#![feature(new_uninit)]
#[derive(Debug, Clone)]
pub struct ObjStorageT {
/// Tab for unique storage
pub tab_: std::collections::HashSet<Box<[u8]>>,
}
impl ObjStorageT {
/// Create new byte storage
pub fn new() -> Self {
return ObjStorageT {
tab_: std::collections::HashSet::<Box<[u8]>>::new(),
};
}
/// Get existing bytes from storage or new new bytes if they don't exist.
pub fn getBytes(&mut self, bytes: &[u8]) -> &[u8] {
if !self.tab_.contains(bytes) {
let mut new_bytes_base: Box<[std::mem::MaybeUninit<u8>]> =
Box::new_uninit_slice(bytes.len());
for i in 0..bytes.len() {
new_bytes_base[i].write(bytes[i]);
}
self.tab_.insert(unsafe { new_bytes_base.assume_init() });
}
let existing_bytes: Option<&Box<[u8]>> = self.tab_.get(bytes);
return existing_bytes.unwrap();
}
}
/// Get storage for ir constants
pub fn globalStorage() -> &'static mut ObjStorageT {
static mut GLBStorageOnce: bool = false;
static mut GLBStorage: std::mem::MaybeUninit<ObjStorageT> = std::mem::MaybeUninit::uninit();
#[allow(unsafe_code)]
unsafe {
if !GLBStorageOnce {
GLBStorageOnce = true;
GLBStorage.write(ObjStorageT::new());
}
return GLBStorage.assume_init_mut();
}
}
#[derive(Debug, Copy, Clone)]
pub struct BytesWrapperT<'a> {
bytes_: &'a [u8],
}
impl BytesWrapperT<'_> {
/// New new constant w/ global obj storage
pub fn new<'a, 'b>(store_: &'a mut ObjStorageT, bytes: &[u8]) -> BytesWrapperT<'a> {
let new_bytes = store_.getBytes(bytes);
return BytesWrapperT { bytes_: new_bytes };
}
/// New new constant w/ global obj storage
pub fn newGlobal<'g>(bytes: &[u8]) -> BytesWrapperT<'g> {
let new_bytes = globalStorage().getBytes(bytes);
return BytesWrapperT { bytes_: new_bytes };
}
pub fn isZeros(self) -> bool {
return self.bytes_.into_iter().all(|x| *x == 0);
}
}
// Doesn't Compile
fn main() {
let mut store: ObjStorageT = ObjStorageT::new();
let mut arr0: [u8; 64] = [0; 64];
let bw0 = BytesWrapperT::new(&mut store, &arr0);
assert!(bw0.isZeros());
arr0[0] = 1;
let bw1 = BytesWrapperT::new(&mut store, &arr0);
assert!(!bw1.isZeros());
assert!(bw0.isZeros());
}
问题是这段代码无法编译,因为
&mut store
被 bw0
和 bw1
借用,即:
74 | let bw0 = BytesWrapperT::new(&mut store, &arr0);
| ---------- first mutable borrow occurs here
...
78 | let bw1 = BytesWrapperT::new(&mut store, &arr0);
| ^^^^^^^^^^ second mutable borrow occurs here
79 | assert!(!bw1.isZeros());
80 | assert!(bw0.isZeros());
| --- first borrow later used here
我认为问题在于,在
BytesWrapperT::new
中,BytesWrapperT<'a>
返回的生命周期与传入商店store_: &'a mut ObjStorageT
的生命周期相同,但我不知道如何获取它,以便我只能返回生命周期与新的 ByteWrapperT
一样长,而不要求它与 ObjStorageT
一样长(仅要求的持续时间不长于 ObjStorageT
)。
我尝试将实现更改为:
impl<'a, 'b> BytesWrapperT<'b> {
/// New new constant w/ global obj storage
pub fn new(store_: &'a mut ObjStorageT, bytes: &[u8]) -> BytesWrapperT<'b> {
let new_bytes = store_.getBytes(bytes);
return BytesWrapperT { bytes_: new_bytes };
}
// ...
}
Playground 但这不起作用,因为
ObjStorageT::getBytes
的返回生命周期不匹配。
我能够使用全局静态变量来编译代码(请参阅Playground),但说实话,从生命周期的角度来看,我真的不明白那可以做什么,但采用
&mut store_
参数的那个却不能
任何帮助
&mut store
和将不胜感激。
谢谢!
编辑: 请原谅风格警告:(
您可以使用不安全的代码做您想做的事情,但您很可能不需要它。您可以选择性能几乎相同的安全等效产品。
而不是
Box<[u8]>
,只需存储 Arc<[u8]>
(或 Rc<[u8]>
)。每次分配只需要几个字节,而引用计数增量则需要更多的周期,但它可以让您安全地完成这一切:
use std::collections::HashSet;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ObjStorageT {
/// Tab for unique storage
pub tab_: HashSet<Arc<[u8]>>,
}
impl ObjStorageT {
/// Create new byte storage
pub fn new() -> Self {
return ObjStorageT {
tab_: HashSet::new(),
};
}
/// Get existing bytes from storage or new new bytes if they don't exist.
pub fn getBytes(&mut self, bytes: &[u8]) -> Arc<[u8]> {
match self.tab_.get(bytes) {
Some(existing_bytes) => Arc::clone(existing_bytes),
None => {
let bytes = bytes.into();
self.tab_.insert(Arc::clone(&bytes));
bytes
}
}
}
}
#[derive(Debug, Clone)]
pub struct BytesWrapperT {
bytes_: Arc<[u8]>,
}
impl BytesWrapperT {
/// New new constant w/ global obj storage
pub fn new(store_: &mut ObjStorageT, bytes: &[u8]) -> BytesWrapperT {
let new_bytes = store_.getBytes(bytes);
return BytesWrapperT { bytes_: new_bytes };
}
/// New new constant w/ global obj storage
pub fn newGlobal(bytes: &[u8]) -> BytesWrapperT {
let new_bytes = globalStorage().getBytes(bytes);
return BytesWrapperT { bytes_: new_bytes };
}
pub fn isZeros(self) -> bool {
return self.bytes_.into_iter().all(|x| *x == 0);
}
}
但是,无法更改插入的字节。我认为这是一个很好的属性:它们是共享的,并且您不希望有人在您背后更改您的字节。如果您确实愿意,可以将它们包裹在
Mutex
或 RwLock
中。