在不安全的Rust中存储对结构体内部数据的静态引用是否法律的?

lstz6jyr  于 2023-06-06  发布在  其他
关注(0)|答案(1)|浏览(193)

我有以下数据结构(简化):

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]
    }
}
hwazgwia

hwazgwia1#

**TL;DR:**这很好。

这里有三个潜在的问题:
1.创建一个引用,其生存期比真实的生存期长。
1.移动类型可能会使引用无效。
1.在删除数据时,我们可以在删除字符串后使用它们。
实际上没有一个是问题。让我们从第一个开始。
创建一个生命周期比实际生命周期长的引用绝对是好的。很多代码都依赖于它来运行,但它并没有列在参考文献中的“行为被认为是未定义的”下面(尽管该列表并不详尽),Rust中所有当前的代码执行模型(如Stacked Borrows或MiniRust)使用此原则(事实上,存在Stacked Borrows的真正原因是不依赖于生命周期的可靠性,而是具有更细粒度的模型),在UCG#231中,声明了生命周期显然不会影响优化,只是当前没有在某处指定。
所以我们现在来谈第二个问题。问题是移动StringCache(以及它的storage)是否会使引用无效,因为它也会移动Vec的元素。或者使用更多的技术术语,无论是移动Vecretags(一个Stacked Borrows术语)它的元素,Assert它们的唯一性。
你的直觉可能会说这很好,但现实更为复杂。VecUnique定义了它的元素,这意味着根据法律的规定,移动它 * 确实 * 使所有现有的指向元素的指针无效(Box也是如此)。然而,很多代码都依赖于这个值为false,所以至少对于Vec(可能还有Boxwe probably want this to be false来说是这样。我认为可以依赖它。Miri也没有给予Vec任何特殊的处理,据我所知,编译器也没有基于它进行优化。
对于(3),您当前的定义(在storage之前声明table)显然是好的,因为它首先删除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);
}

这段代码之所以能够成功编译,是因为HashMap承诺在drop过程中不会触及它的元素。否则,我们可以使用后免费。所以HashMap不能突然改变这个事实。

相关问题