提问人:Noah 提问时间:10/24/2023 最后编辑:Noah 更新时间:10/24/2023 访问量:47
集合中的 Rust 返回对象,其生存期短于集合本身
Rust return object in collection with lifetime shorter than the collection itself
问:
我正在尝试使用.这个想法是,我有一个包装字节引用的结构,在创建新对象时,我检查它包装的字节是否已经在使用中,如果是,则重用现有的堆分配字节。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
我认为问题在于,在返回的生命周期中,与传入存储的生命周期相同,但我无法弄清楚如何获取它,以便我只能返回新的生命周期,而不要求它像 (只有 req 持续时间不超过 )。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_
任何帮助
- 弄清楚如何配置我可以重用的生存期 s.t 和
&mut store
- 为什么静态版本可以工作
将不胜感激。
谢谢!
编辑: 原谅样式警告:(
答:
1赞
Chayim Friedman
10/24/2023
#1
您可以使用不安全的代码做您想做的事,但很可能您不需要它。您可以使用性能几乎相同的安全等效物。
而不是 ,只是存储 (或 )。每次分配只有几个字节,而 refcount 增量则多几个周期,但它可以让您安全地完成所有操作: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
评论
0赞
Noah
10/24/2023
谢谢。认为这可能是最有道理的,但你有机会向我指出解决终身问题的不安全技术吗?
1赞
Chayim Friedman
10/24/2023
@Noah 如果你是 Rust 的初学者,你不需要它。如果你不知道你是否需要它,你就不需要它。如果你需要它,你就会知道。如果你只想学习,神奇的诀窍就是让。它类似于 arena,只是大多数 arena 实现在分配上产生,并且从不重用相同的分配,并且您希望 yo yield()(否则重用是不合理的)并在现有记录中搜索。getBytes()
&self
&mut T
&T
&[u8]
0赞
Noah
10/24/2023
很公平。为什么静态版本有效,而参数 1 不起作用?似乎两者都应该遭受同样的问题,即持有对一家比谁更久的商店的引用?
0赞
Chayim Friedman
10/24/2023
这两个版本是什么意思?
评论
camelCase
snake_case
Box<[u8]>
From<&[u8]>