深入理解 Rust 的c_void:核心源码实现、FFI 用法与 opaque 类型建模
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
导读
c_void是 Rust 标准库中用于对接 C 语言void*/const void*指针类型的关键类型,是编写 FFI(外部函数接口)绑定、操作原生句柄与内存接口时几乎绕不开的基础设施。本文以 library/core/src/ffi/c_void.md 官方文档为骨架,深入当前仓库源码,讲解c_void的定义方式、与 C 类型体系的对应关系、绕过extern type之前的 opaque 类型建模方案,以及它在标准库内部(如VaList、Windows 进程扩展、LLVM 位码表示)的真实落地用法,帮助你写出正确、安全、可维护的 FFI 代码。
一、c_void是什么:C 的void*在 Rust 中的对应物
c_void是 Rust 标准库提供的、与 C 语言void类型在指针语境下等价的核心 FFI 类型。它定义在 library/core/src/ffi/mod.rs 中,通过include_str!把 c_void.md 的内容作为该类型的官方文档:
#[doc = include_str!("c_void.md")] #[lang = "c_void"] #[repr(u8)] #[stable(feature = "core_c_void", since = "1.30.0")] pub enum c_void { #[unstable(feature = "c_void_variant", reason = "temporary implementation detail", issue = "none")] #[doc(hidden)] __variant1, #[unstable(feature = "c_void_variant", reason = "temporary implementation detail", issue = "none")] #[doc(hidden)] __variant2, }从源码可以看出几个关键事实:
- 它是一个枚举,而不是单元结构体(unit struct)。
#[repr(u8)]指明其内存表示占用一个字节;私有且#[doc(hidden)]的两个变体__variant1、__variant2保证了外部代码永远无法构造或匹配c_void的实例。 - 为什么需要两个变体:mod.rs 第 39~46 行的注释说明得很清楚——编译器会抱怨
repr属性需要一个以上的变体,而至少要有一个变体,否则枚举将是“无人居住”(uninhabited)的,对这类指针解引用会构成未定义行为(UB)。所以两个变体是一个精心的工程折衷。 - 为什么是枚举而非
():这是为了让 LLVM 在生成位码时把*const c_void识别为i8*。mod.rs 的注释("for LLVM to recognize the void pointer type and by extension functions like malloc()")明确指出:只有以i8*表示 void 指针,malloc等 C 运行时函数才能被 LLVM 正确识别与优化。
与 C 类型体系的等价关系
官方文档给出最核心的对应关系:
*const c_void等价于 C 的const void*;*mut c_void等价于 C 的void*;- 注意:
c_void不是 C 的void返回类型——C 的void返回类型对应 Rust 的()(单元类型)。也就是说,声明一个 C 函数void foo(void)时,在 Rust 里应写成extern "C" fn foo(),返回值是(),而绝不是c_void。
版本与路径演进
- Rust 1.30.0 起,
c_void从std::os::raw迁入core::ffi并由std::ffi再导出(library/std/src/ffi/mod.rs 中的pub use core::ffi::c_void;即为此再导出的实现); std::os::raw目前只是兼容层,在 library/std/src/os/raw/mod.rs 中用alias_core_ffi!宏把c_void等类型直接type别名为core::ffi::c_void,其文档同样是include_str!引入的c_void.md;- 历史背景可参考 RFC 2521(
c_void统一方案),它推动了旧编译器时代std::os::raw::c_void与新路径core::ffi::c_void的归一化。如果你的代码要兼容早至 1.1.0 的编译器,可以继续使用std::os::raw::c_void;在 1.30.0 之后,二者是同一类型。
二、FFI 中的典型用法:句柄、不透明指针与 C 运行时函数
1. 把c_void用作“万能”指针参数/返回值
在 C 侧,大量 API 以void*传递任意数据或句柄。Rust 侧对应的就是*mut c_void或*const c_void。当前仓库中一个非常典型的示例来自 library/std/src/os/windows/process.rs 的文档示例:调用 Windows APICreatePipe时,其安全属性指针lppipeattributes的类型即*const c_void(传std::ptr::null()表示使用默认安全属性):
#![feature(windows_process_extensions_raw_attribute)] use std::ffi::c_void; use std::os::windows::process::{CommandExt, ProcThreadAttributeList}; use std::os::windows::raw::HANDLE; use std::process::Command; #[repr(C)] pub struct COORD { pub X: i16, pub Y: i16, } unsafe extern "system" { fn CreatePipe( hreadpipe: *mut HANDLE, hwritepipe: *mut HANDLE, lppipeattributes: *const c_void, // NULL 表示默认安全属性 nsize: u32, ) -> i32; // ... }另一个常见场景是 Windows 句柄类型。在 library/std/src/os/windows/raw.rs 中,HANDLE被定义为:
pub type HANDLE = *mut c_void;也就是说,Windows 的HANDLE在 Rust 中就是一个指向c_void的可变裸指针。整个std::os::windows::process扩展(如ProcThreadAttributeList、伪控制台CreatePseudoConsole)都围绕它工作,相关代码可见 library/std/src/os/windows/process.rs。
2. 标准库内部的真实使用:VaList、malloc与回溯
c_void不只是给用户用的,标准库自身也在大量使用:
- 变参函数:
VaList是与 Cva_listABI 兼容的类型(library/core/src/ffi/va_list.rs),其内部VaListInner结构体在 x86_64、AArch64、RISC-V、MIPS、PowerPC 等架构上大量使用*const c_void字段(如overflow_arg_area、reg_save_area、__current_saved_reg_area_pointer等,见 va_list.rs)。同时 va_list.rs 专门为*const c_void/*mut c_void实现了VaArgSafe标记 trait,允许在 C 变参函数中读取 void 指针类型的参数。 - C 运行时函数:在 library/core/src/ffi/mod.rs 的
runtime_symbols模块中,memcpy、memmove、memset、memcmp、bcmp等 C 运行时符号的原型全部使用*mut c_void/*const c_void。这也是“只有以i8*表示 void 指针 LLVM 才能正确识别malloc”这一设计动机的直接体现。 - 回溯:
std的 backtrace 实现用fn ip(&self) -> *mut c_void表示指令指针(library/std/src/backtrace.rs)。
三、如何在 FFI 中建模“不透明类型”(opaque types)
c_void最常见的误用,是把*mut c_void直接当成一切不透明类型的万能容器。官方文档对此给出了明确的、更优的工程建议:
在
extern type稳定之前,要建模指向不透明类型的指针(例如指向 C 侧未公开内部结构的struct Foo的指针),推荐使用“包着一个空字节数组的新类型包装器”(newtype wrapper around an empty byte array)。
具体做法是:
// 不透明类型的占位:空字节数组 #[repr(C)] pub struct Opaque { _data: [u8; 0], } extern "C" { fn foo_new() -> *mut Opaque; // 等价于 C 的 struct Foo* fn foo_set(obj: *mut Opaque, v: i32); fn foo_free(obj: *mut Opaque); }这种做法的意义在于:相比*mut c_void,*mut Opaque携带了类型信息,编译器可以阻止你把foo_new()的返回值错误地传给另一个不透明类型的函数(类型安全),同时又不会引入对内部布局的任何假设。该方案的详细原理与历史可参见 Rust Nomicon 中 “Representing Opaque Structs” 一节(官方文档 c_void.md 中给出的 [Nomicon] 链接)。
关于extern type的进展:这是一个自 Rust 1.23.0 起就在 unstable 特性列表中跟踪的语言特性(见 compiler/rustc_feature/src/unstable.rs,跟踪 issue 为 43467)。从当前仓库源码看,它仍然处于 unstable 状态(对应 feature gate 为extern_types),因此在写稳定代码时,空字节数组包装方案依然是官方推荐的默认做法。一旦extern type稳定,就可以直接写extern "C" { type Foo; }来获得更自然的语法。
四、Debug实现与安全注意
c_void在 library/core/src/ffi/mod.rs 中实现了Debug:
#[stable(feature = "std_debug", since = "1.16.0")] impl fmt::Debug for c_void { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("c_void").finish() } }打印一个c_void会输出形如c_void的结构体调试信息(不展开任何字段),这使你在调试 FFI 代码时可以安全地{:?}打印指针,而无需关心其指向的内容。
使用c_void时还需要牢记 Rust 裸指针的基本原则:
*const c_void/*mut c_void是裸指针,编译器不保证其合法性,解引用或传递给 C 函数属于unsafe操作;- 不要把
c_void与()混淆:前者是"指向任意字节的不透明指针"的类型参数,后者才是"无返回值"; - 在声明 C 函数原型时,务必保证参数/返回值类型与 C 头文件逐位一致,
void*对应*mut c_void或*const c_void(取决于是否被修改),void返回值对应()。
五、快速参考:何时用c_void,何时不该用
| 场景 | 推荐写法 |
|---|---|
声明 C 函数,参数为void* | *mut c_void(可写)/*const c_void(只读) |
声明 C 函数,返回void* | *mut c_void |
声明 C 函数,返回void | 返回值写() |
| 建模不透明 C 结构体指针(稳定版) | 空字节数组 newtype 包装,如struct Opaque { _data: [u8; 0] } |
| 建模不透明 C 结构体指针(未来、unstable) | extern type(feature gate:extern_types) |
WindowsHANDLE | *mut c_void(即 library/std/src/os/windows/raw.rs 中的类型别名) |
| C 变参函数中读取 void 指针参数 | VaList::next_arg::<*const c_void>()等,配合VaArgSafe |
总结
c_void是 Rust FFI 生态的基石类型:它在 library/core/src/ffi/mod.rs 中被精心实现为带两个私有变体的#[repr(u8)]枚举,既保证了 LLVM 能将其识别为i8*以便正确优化malloc/memcpy等 C 运行时函数,又通过私有变体杜绝了用户构造实例的可能。它的等价关系很简单——*const c_void对应const void*、*mut c_void对应void*、void返回值对应()——而真正的高质量 FFI 代码,在建模不透明类型时应优先采用空字节数组 newtype 包装,直到extern type特性稳定。理解了它的定义动机、版本沿革与标准库内部用法(VaList、HANDLE、backtrace),你就能写出类型更安全、行为更可预期的 FFI 绑定。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考