Rust中跨crate引用相同字符串常量时指针为何不一致?
问题背景
使用&'static str作为HashMap键类型时,为了降低哈希、相等比较的开销,我基于newtype模式实现了一个包装类型,通过指针而非字符串内容完成哈希计算与相等判定,实现代码如下:
pub struct StaticStr(&'static str); impl Hash for StaticStr { fn hash<H: Hasher>(&self, state: &mut H) { self.0.as_ptr().hash(state) } } impl PartialEq for StaticStr { fn eq(&self, other: &Self) -> bool { self.0.as_ptr() == other.0.as_ptr() } } impl Eq for StaticStr {}
该实现无法稳定工作,复现代码如下:
pub type MyMap = HashMap<StaticStr, u8>; pub const A: &str = "A"; pub fn make_map() -> MyMap { let mut map = MyMap::new(); map.insert(StaticStr(A), 1); map } pub fn get_value(control: &MyMap) -> Option<u8> { control.get(&StaticStr(A)).cloned() } #[cfg(test)] mod tests { use super::*; #[test] pub fn map_made_in_lib() { let map = make_map(); assert_eq!(get_value(&map), Some(1)); } #[test] pub fn map_made_in_test() { // 与make_map()逻辑完全一致 let mut map = MyMap::new(); map.insert(StaticStr(A), 1); // 该断言执行失败 assert_eq!(get_value(&map), Some(1)); } }
实际测试观测到的现象:
- 第一个测试中字符串常量
A仅在lib crate内直接使用,测试可正常通过 - 第二个测试中
A同时在lib crate和测试crate内被直接引用,断言失败 - 验证确认:即使是同一个字符串常量,根据引用它的crate不同,对应的指针值也可能存在差异。
我原本预期定义该常量的crate只会存储一份字符串字面量,或至少链接器会自动对重复的字符串字面量做去重处理,想了解该行为的设计原因。
原因说明
该实现失效的核心原因是:Rust语言规范从未保证内容相同的&'static str字面量会持有相同的内存指针,靠指针相等判定静态字符串等价属于不受规范保证的行为,没有跨场景稳定性,具体逻辑分为两点:
const常量的内联语义:Rust中用const定义的&str不是指向全局唯一内存位置的静态变量,而是会在所有使用位置直接内联展开的常量值。测试代码和被测试的lib属于两个独立的代码生成单元,各自内联同内容的字符串字面量时,编译器不会主动将它们分配到同一内存地址。- 字符串字面量去重不是强制要求:链接器合并相同只读字符串只是部分工具链在特定优化级别下的可选优化,不是语言标准、也不是通用二进制格式要求的强制行为:
- Debug构建默认关闭优化时,几乎所有链接器都不会执行字符串合并
- 即使开启最高级别优化,跨crate、尤其是跨动态链接库的字符串字面量也经常不会被合并
- Rust编译器本身从来不对
&str的指针相等性做任何语义承诺,相同内容的&'static str返回不同指针是完全合法的正常行为。
正确实现参考
如果需要低开销的静态字符串作为HashMap键,不要依赖指针判等:
- 绝大多数场景直接使用
&'static str作为键即可,Rust对短字符串的哈希、相等比较开销极低,指针比较带来的性能差异在实际业务中几乎可以忽略 - 如果确实需要极致性能,可以通过过程宏在编译期为每个静态字符串分配全局唯一的整数ID,基于整数ID做哈希和相等判定,这种方式的行为完全确定,跨编译单元稳定。
内容的提问来源于stack exchange,提问作者Tim Harding
相关产品推荐
相关产品推荐

