这个 mem::transmute::<&str, &'static str>(k) 安全吗?

3

我正在查看这段代码,它是一个非常简单的库,只有一个文件和大部分测试,所以很短。我想要理解其中的结构体:

pub struct ChallengeFields(HashMap<UniCase<CowStr>, (String, Quote)>);

struct CowStr(Cow<'static, str>);

有一行代码的作用是

pub fn get(&self, k: &str) -> Option<&String> {
    self.0
        .get(&UniCase(CowStr(Cow::Borrowed(unsafe {
            mem::transmute::<&str, &'static str>(k)
        }))))
        .map(|&(ref s, _)| s)
}

我对这个不安全的操作感到烦恼。我认为`CowStr`是一个具有静态生命周期的`Cow`,否则将难以或不可能将`str`存储在map中。正因如此,当我尝试获取map中的某些内容时,所需的`str`必须具有`'static`生命周期。这就是使用`transmute`的原因,对吗?如果是这样,为什么不简单地使用`String`,这样我们就可以摆脱生命周期和`transmute`了呢?我不喜欢`unsafe`,而且阅读关于`transmute`的说明后,它看起来非常不安全。
另外,我不明白为什么需要使用`Cow`。
1个回答

3
我认为 CowStr 是一个具有 'static 生命周期的 Cow,否则将难以或不可能在 map 中存储字符串。
是和不是。您可以毫无问题地在哈希映射表中存储 &'static str,但问题是您不能同时存储 both &'static strString
我猜这是一个优化:使用 String 每次想要在 map 中插入数据就必须创建一个分配,如果绝大多数的挑战名称都是 DigestBasic 那将是浪费时间(主要是时间而不是内存),但同时您必须支持 String 用于定制的身份验证方案。
也许从更大的方面来看,这并不是一项真正重要的优化,最好不要这样做,我不能告诉您。
我不喜欢不安全,读了一下 transmute 的介绍,它看起来非常不安全。
这是可争议的事情,但在这种情况下是“安全”的,因为该引用在整个 HashMap::get 调用期间都是有效的,而我们知道该调用不会保留引用(它依赖于一种实现细节有点冒险,但它发生变化的可能性基本为零,因为这没有多大意义)。
扩展生命周期本身并不属于未定义行为(mem::transmute 文档提供了一个示例),但需要小心,因为您必须避免它导致 UB(最有可能导致悬空引用)。

不,CowStr只是一个新类型,它在运行时并没有什么作用。而非空的String会触发堆分配。 - Masklinn
直接在哈希映射表中操作。虽然 CowStr::Owned 暗示了一个包含堆分配缓冲区的 String - Masklinn
但是哈希映射表不是持有堆分配的内存吗? - Gatonito
是的,哈希映射表确实有自己的缓冲区,但它与字符串的缓冲区是独立的,并且是摊销的。 - Masklinn
1
或许你可以这样理解答案中的“创建分配”部分:将其想象为“创建一个额外的分配”。是的,HashMap::insert 会进行分配,但如果它使用 String 作为键,则需要为拥有的字符串进行额外的分配,这是可以避免的,并且(在代码作者的显然看法中)是不必要的。 - user4815162342
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接