代码之家  ›  专栏  ›  技术社区  ›  ChrisB

在不安全的Rust中存储对结构内部数据的“静态引用”合法吗

  •  1
  • ChrisB  · 技术社区  · 2 年前

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

    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]
        }
    }
    
    0 回复  |  直到 2 年前
        1
  •  8
  •   Chayim Friedman    2 年前

    TL;博士: 这很好。


    这里有三个潜在的问题:

    1. 创建一个使用寿命比实际使用寿命长的引用。
    2. 移动类型可能会使引用无效。
    3. 在删除数据时,我们可以在删除数据后使用字符串。

    实际上,它们都不是问题。让我们从第一个开始。

    创建一个生存期比实际生存期长的引用是 肯定 好的野外的许多代码都依赖于它才能正常工作, it is not listed under "Behavior considered undefined" 在参考中(尽管该列表并非详尽无遗),Rust中代码执行的所有当前模型(如Stacked Borrows或 MiniRust )采用这一原理(事实上,Stacked Borrows存在的原因是不依赖寿命来保证可靠性,而是拥有更细粒度的模型),并且 UCG#231 有人指出,很明显,生存期不会影响优化,只是目前某个地方没有指定。

    所以我们来讨论第二个问题。问题是移动 StringCache (因此 storage )将使引用无效,因为它将移动 Vec 的元素。或者用更专业的术语,无论移动 Vec 重新标记 (堆积借款术语)其元素,声称其独特性。

    你的直觉可能会说这很好,但现实更复杂。 Vec 用定义其项 Unique ,这意味着通过法律条文来移动它 使所有现有的指向元素的指针无效(关于 Box 也然而,许多野生代码都依赖于这一点是错误的,所以至少对于 Vec (也许是为了 we probably want this to be false 。我认为可以相信这一点。米丽不给 Vec 任何特殊处理,据我所知,编译器也不会基于它进行优化。

    对于(3),您当前的定义(声明 table 之前 存储 )很明显很好,因为它会降低 HashMap 首先,但即使是您以前的定义(声明 存储 第一)很好,因为 HashMap is declared with #[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);
    }
    

    此代码编译成功是因为 哈希图 承诺在掉落过程中不会接触其元素。否则,我们可以免费使用。所以 哈希图 不能突然改变这个事实。