我有以下数据 struct (简化):

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撒谎是否合法/defined behaviour?对我来说,这感觉就像违反了类型系统. 这个数据 struct 的公共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]
    }
}

推荐答案

TL;DR:元,这很好.


这里有三个潜在的问题:

  1. 创建生存期长于其实际生存期的引用.
  2. 移动类型可能会使引用无效.
  3. 在删除数据时,我们可以在它们被删除后使用字符串.

实际上,这些都不是问题.让我们从第一个开始.

创建一个生存期长于其实际生存期的引用是可以的.自然环境中的许多代码都依赖于它,参考文献中的it is not listed under "Behavior considered undefined"(即使该列表不是详尽的),所有当前在Rust中执行代码的模型(例如堆叠借阅或MiniRust)都使用这一原则(事实上,堆叠借阅存在的真正原因是不依赖于生命周期来保证可靠性,而是具有更细粒度的模型),并且在UCG#231中声明生命周期显然不会影响优化,只是目前没有在某个地方指定而已.

所以我们要谈到第二个问题.问题是,移动StringCache(因此,它的storage)是否会使引用无效,因为它也会移动Vec‘S元素.或者用更专业的术语,无论是移动Vec retags(堆叠的借词)它的元素,断言它们的唯一性.

你的直觉可能会说这很好,但现实要复杂得多.VecUnique定义它的项,这意味着按照法律的规定,将它移动到does将使所有现有的指向元素的指针无效(Box也是如此).然而,自然环境中的许多代码都依赖于这一点是错误的,因此至少对于Vec(也许Box也是如此)we probably want this to be false是错误的.我认为依靠这一点是可以的.Miri也没有给Vec任何特殊待遇,据我所知,编译器也不会基于它进行优化.

对于(3),您当前的定义(在storage之前声明table)显然是可以的,因为它首先删除了HashMap,但即使是您之前的定义(首先声明storage)也是可以的,因为HashMap is declared with #[may_dangle],这意味着它promise 不会在其删除中访问它的元素(除了删除它们之外).这也是一种 solidity 保证,因为即使在与您的代码非常相似的代码中也可以观察到:

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);
}

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

Rust相关问答推荐

重新导出proc宏导致未解决的extern crate错误""

如何对字符串引用的引用向量进行排序,而不是对最外层的引用进行排序?

如何将`Join_all``Vec<;Result<;Vec<;Foo&>;,Anywhere::Error&>;`合并到`Result<;Vec<;Foo&>;,Anywhere::Error&>;`

如何导入crate-type=[";cdylib;]库?

如何高效地将 struct 向量中的字段收集到单独的数组中

为什么&;mut buf[0..buf.len()]会触发一个可变/不可变的borrow 错误?

返回Result<;(),框<;dyn错误>>;工作

.在 Rust 模块标识符中

为什么切片时需要参考?

方法可以被误认为是标准特性方法

枚举的利基优化如何在 Rust 中工作?

pyO3 和 Panics

简单 TCP 服务器的连接由对等重置错误,mio 负载较小

分配给下划线模式时会发生什么?

一个函数调用会产生双重borrow 错误,而另一个则不会

rust 中不同类型的工厂函数

返回引用字符串的future

在 Rust 中返回对枚举变体的引用是个好主意吗?

在 Rust 中退出进程

Rust 为什么 (u32, u32) 的枚举变体的大小小于 (u64)?