Rust中的`*const ()`究竟是什么?与C/C++的`void*`有何区别?
Rust 中
*const ()的核心本质 *const ()是指向零大小单元类型()的不可变原始裸指针。由于()本身是不占任何存储空间的零大小类型(ZST),指向它的指针不会携带任何“指向数据的解析规则、对齐要求、读取宽度”这类类型元信息,内存层面只需要存储一个纯地址值即可,布局上和C/C++中的void*几乎完全一致。
和C/C++
void*的异同 - 相似点:它的核心用途就是当“不绑定具体指向类型的通用地址容器”,可以承载任意合法的内存地址值,用来在不同指针类型之间做中转传递,这和C里
void*作为通用指针的设计目标完全重合。 - 核心差异:Rust对裸指针转换的规则比C严格得多,不会默认允许所有类型随意转成通用指针,所有有风险的转换都会被编译器拦截,必须走显式、需要手动标注unsafe的路径完成。
为什么整数、函数能直接
as转*const (),浮点数却不行 Rust的as强制转换采用严格的白名单机制,只有被明确允许的类型对才能一步完成转换:
- 允许直接转成
*const ()的源类型只有两类:整数类型(不管是否和系统字长匹配,编译器会自动根据指针宽度做截短或符号扩展适配)、其他类型的裸指针、函数指针。这些类型从语义上天然和“存储地址”的概念相关,因此允许直接转换。你示例里的i32类型整数能直接转*const ()就是这个规则的体现。 - 浮点数不被允许直接
as转成指针的原因是:浮点数的语义是表示连续的小数值,本质上和“内存地址”的概念没有天然关联,如果放开直接转换,很容易诱导开发者无意义地把小数的位模式当成内存地址用,引入大量难以排查的未定义行为。
如果确实需要把浮点数的位模式转成指针,完全可以通过显式的链式转换完成,比如1.0f64 as u64 as *const (),这种两步显式转换是被编译器完全允许的。
为什么官方
transmute示例要拿*const ()当中转类型 std::mem::transmute的核心约束是:两个参与转换的类型尺寸必须完全相等,且整个转换过程必须手动标记为unsafe(它不做任何类型检查,本质是对内存位模式做无检查的重解释)。
之所以选*const ()当中转,是因为Rust对函数指针的转换做了严格的安全限制:不同签名的函数指针之间不能直接通过as转换,也不允许直接在两个不同类型的函数指针之间做transmute——如果放开这个限制,很容易出现“拿参数签名不匹配的函数指针直接调用”的严重错误。
而*const ()作为没有类型绑定的通用地址容器,正好成为了合规的中间站:
- 任意函数指针、数据指针都可以合法通过
as转成*const (),这步是安全的(仅存储地址值,不涉及任何解引用操作) - 再把这个
*const ()通过transmute转成目标函数指针类型,这步会被编译器强制要求放在unsafe块里,把“把地址重解释成可调用函数”的风险完全暴露给开发者,符合Rust的安全设计原则。
补充注意:
*const ()和C的void*有一个不能踩的坑——你永远不能直接解引用*const (),因为它指向的()是零大小类型,解引用不会得到任何有实际意义的数据。所有涉及读取内存内容、调用函数的操作,都必须手动把它转成正确的目标类型后,在unsafe块里完成。
内容的提问来源于stack exchange,提问作者Jason Yu
相关产品推荐
相关产品推荐

