Chr*_*isB 6 unsafe lifetime language-lawyer rust
我有以下数据结构(简化):
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>>,
}
Run Code Online (Sandbox Code Playgroud)
关于此处引用的生命周期向 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]
}
}
Run Code Online (Sandbox Code Playgroud)
TL;DR:这很好。
这里存在三个潜在的问题:
实际上它们都不是问题。让我们从第一个开始吧。
创建一个生命周期比其实际生命周期长的引用绝对没问题。许多野外代码都依赖它来正常工作,它没有在参考文献中的“行为被视为未定义”下列出(即使该列表并不详尽),Rust 中当前所有代码执行模型(例如 Stacked Borrows或MiniRust)采用了这一原则(事实上,Stacked Borrows 存在的真正原因是不依赖生命周期来实现稳健性,而是拥有更细粒度的模型),并且在UCG#231中指出,很明显,生命周期不会影响优化,只是当前未在某处指定。
那么我们就来讨论第二个问题。问题是移动StringCache(以及其storage)是否会使引用无效,因为它也会移动Vec的元素。或者使用更多技术术语,无论是移动Vec 重新标记(堆栈借用术语)其元素,还是断言其独特性。
你的直觉可能会说这很好,但现实要复杂得多。Vec用 定义其项目Unique,这意味着根据法律条文,移动它确实会使所有现有的指向元素的指针无效(这也是如此Box)。然而,许多野外代码都依赖于它是 false ,所以至少对于Vec(也许Box也是)我们可能希望它是 false。我认为依靠这一点是可以的。Miri也没有给予Vec任何特殊处理,据我所知,编译器也没有基于它进行优化。
对于 (3),您当前的定义(在table之前声明storage)显然没问题,因为它删除了第HashMap一个,但即使您以前的定义(storage首先声明)也很好,因为HashMap是用 声明的#[may_dangle],这意味着它承诺不会访问其元素(除了掉落它们之外)。这也是稳定性保证,因为它是可观察的,即使在与您的代码非常相似的代码中:
use std::collections::HashMap;
#[derive(Default)]
struct StringCache<'a> {
storage: Vec<Box<str>>,
table: HashMap<&'a str, usize>,
}
fn main() {
let mut string_cache = StringCache::default();
string_cache.storage.push("hello".into());
string_cache.table.insert(&string_cache.storage[0], 0);
}
Run Code Online (Sandbox Code Playgroud)
该代码编译成功的事实是因为HashMap承诺在放置期间不会触及其元素。否则,我们可以有释放后使用。所以HashMap不能突然改变这个事实。