我有以下数据结构(简化):
use std::collections::HashMap;
pub struct StringCache {
// the hashmap keys point to elements stored in `storage`
// so never outlive this datastructure. the 'static refs are never handed out
table: HashMap<&'static str, usize>,
storage: Vec<Box<str>>,
}
在这里向Rust“撒谎”引用的生命周期是否法律的/* 定义的行为 *?我觉得这就像违反了类型系统。这个数据结构的公共API是否合理?为了完整起见,下面是完整的实现:
use std::mem::transmute;
impl StringCache {
pub fn intern(&mut self, entry: &str) -> usize {
if let Some(key) = self.table.get(entry) {
return *key;
}
let idx = self.storage.len();
self.storage.push(entry.to_owned().into_boxed_str());
// we cast our refs to 'static here.
let key = unsafe { transmute::<&str, &'static str>(&self.storage[idx]) };
self.table.insert(key, idx);
idx
}
pub fn lookup(&self, idx: usize) -> &str {
&self.storage[idx]
}
}
1条答案
按热度按时间hwazgwia1#
**TL;DR:**这很好。
这里有三个潜在的问题:
1.创建一个引用,其生存期比真实的生存期长。
1.移动类型可能会使引用无效。
1.在删除数据时,我们可以在删除字符串后使用它们。
实际上没有一个是问题。让我们从第一个开始。
创建一个生命周期比实际生命周期长的引用绝对是好的。很多代码都依赖于它来运行,但它并没有列在参考文献中的“行为被认为是未定义的”下面(尽管该列表并不详尽),Rust中所有当前的代码执行模型(如Stacked Borrows或MiniRust)使用此原则(事实上,存在Stacked Borrows的真正原因是不依赖于生命周期的可靠性,而是具有更细粒度的模型),在UCG#231中,声明了生命周期显然不会影响优化,只是当前没有在某处指定。
所以我们现在来谈第二个问题。问题是移动
StringCache
(以及它的storage
)是否会使引用无效,因为它也会移动Vec
的元素。或者使用更多的技术术语,无论是移动Vec
retags(一个Stacked Borrows术语)它的元素,Assert它们的唯一性。你的直觉可能会说这很好,但现实更为复杂。
Vec
用Unique
定义了它的元素,这意味着根据法律的规定,移动它 * 确实 * 使所有现有的指向元素的指针无效(Box
也是如此)。然而,很多代码都依赖于这个值为false,所以至少对于Vec
(可能还有Box
)we probably want this to be false来说是这样。我认为可以依赖它。Miri也没有给予Vec
任何特殊的处理,据我所知,编译器也没有基于它进行优化。对于(3),您当前的定义(在
storage
之前声明table
)显然是好的,因为它首先删除HashMap
,但即使是您以前的定义(首先声明storage
)也是好的,因为HashMap
是用#[may_dangle]
声明的,这意味着它承诺不会在其删除中访问其元素(除了删除它们)。这也是一个稳定性保证,因为它是可观察的,即使在与您的代码非常相似的代码中:这段代码之所以能够成功编译,是因为
HashMap
承诺在drop过程中不会触及它的元素。否则,我们可以使用后免费。所以HashMap
不能突然改变这个事实。