本次 Rust 1.99.0 的发布,核心亮点在于稳定了 extern "C" variadics(C 语言可变参数列表支持)。此外,新版本还完善了裸指针的布局信息获取,并对 Box::leak 的内存释放规范给出了重要警告,同时稳定了多项底层转换 API,进一步提升了语言的底层开发体验。笔者将带领大家深入探讨这些激动人心的新特性。
extern “C” variadics 支持
解决什么问题:
在以往的 Rust 版本中,开发者可以轻松调用由 C 语言或其他语言编写的外部可变参数函数(例如 libc 中的 printf 家族)。但是,如果想要在 Rust 内部定义一个能够接收可变参数并暴露给 C 语言调用的函数,一直缺乏直接的语言级支持。这使得 Rust 在替换 C 语言底层库,或者编写需要实现特定 C 接口的操作系统驱动程序时,常常需要依赖复杂的宏或内联汇编。
从 1.99 开始,Rust 终于正式允许直接定义具有 "C" 或 "C-unwind" ABI 的可变参数函数。其参数类型明确使用 ... 表示,在 Rust 内部对应的新类型是 VaList。这个类型能够在底层与 C 语言的 va_list 结构实现跨目标平台的 ABI 完全兼容。为了确保安全性,可以从 VaList 中读取的数据类型受到了 VaArgSafe trait 的严格保护。不仅如此,本次更新还稳定了通过内联汇编为非 "C" ABI 编写 naked variadic 函数的支持。
官方示例代码:
/// SAFETY: must be called with (at least) 2 i32 arguments.
unsafe extern "C" fn sum(mut args: ...) -> i32 {
// SAFETY: guaranteed by the caller.
let a = unsafe { args.next_arg::<i32>() };
let b = unsafe { args.next_arg::<i32>() };
a + b
}
fn foo() -> i32 {
unsafe { sum(0i32, 2i32) }
}
通过裸指针获取内存布局信息
解决什么问题:
在进行极致性能优化或构建自定义数据结构时,裸指针操作是不可避免的。而在操作裸指针时,安全地获取其背后数据的内存大小(Size)和对齐信息(Alignment)是一项核心基础需求。对于实现了 Sized 的类型,这相对简单;但对于非 Sized 类型(如 [T] 或 dyn Trait),由于胖指针的设计,直接获取布局信息不仅麻烦,还往往伴随着未定义行为(UB)的风险。
本次发布彻底厘清了这部分的安全性要求,并稳定了一系列专用于裸指针的布局获取函数:
Layout::for_value_rawmem::size_of_val_rawmem::align_of_val_raw
这些函数的稳定意味着开发者可以直接向它们传递裸指针,而无需事先将其转换为引用(这在未完全初始化的内存或借用规则严格限制的场景下极易触发 UB)。这让底层内存分配、回收操作和自定义分配器(Custom Allocators)的实现变得前所未有的安全与便捷。
破坏性注意点:不再建议撤销 Box::leak 的泄露
解决什么问题:
这是一个极其重要的行为规范更新。在 Rust 的内存管理模式中,Box::leak 常常被用来将动态分配的内存泄露,从而获得一个 'static 生命周期的可变引用。过去,一些开发者会采用一种 trick:先将内存 leak 出来使用,在程序某个生命周期末尾,再用 unsafe 方式重新将其构建回 Box 并将其丢弃(Drop),从而达到所谓“撤销泄露”(round-trip unleaking)的目的。
然而,官方文档在 1.99.0 中大幅更新,明确警告开发者不要再采用这种反模式。这是因为编译器优化模型(尤其是未来的 LLVM 优化升级)在看到 Box::leak 时,可能会进行一种被称为“不可达释放”的激进优化假设。如果在 leak 之后再次释放该内存,不仅会破坏编译器的别名分析(aliasing analysis),更可能在自定义分配器稳定后引发不可预测的崩溃问题。
作为更安全的替代方案,官方建议开发者使用 Box::into_non_null 或 Box::into_raw。这两个 API 纯粹只是进行了指针类型的转换,并没有向编译器传达“永不释放”的语义。此指导原则同样适用于标准库中的其他 leak 函数,如 String::leak 等。
核心 API 稳定化
解决什么问题: 除了上述重大特性外,Rust 1.99 还稳定了大量极具实用价值的基础库 API,为开发者在进行复杂数据处理时提供更符合人体工程学的标准方案。
- Vec 与零拷贝拆解:
新增稳定的
Vec::into_parts和Vec::from_parts彻底解决了开发者在处理 C-FFI 时,将Vec拆解为裸指针、长度和容量再重新组合的安全痛点。过去许多开发者在手动拆解并传递结构时,容易因为字段顺序或是借用检查错误导致内存泄漏或悬垂指针。新接口提供了更清晰的结构化数据载体,提升了 FFI 边界的代码安全性。 - 字符串有损转换:
新增的
String::from_utf8_lossy_owned以及string::FromUtf8Error::into_utf8_lossy提供了一种直接将已分配内存字节数组转换为String的方法。如果是无效的 UTF-8 序列,它会在原分配空间上进行有损替换,避免了大量不必要的中间字符串分配,对于编写高性能网络协议解析器极为重要。 - 文件元数据操作与集合更新:
std::fs::set_times和std::fs::set_times_nofollow允许直接对文件系统的时间戳(访问时间与修改时间)进行精细化设置,弥补了长期以来跨平台时间修改的不足。此外Box::into_non_null和Box::from_non_null接口的稳定,以及为Box<[T; N]>引用实现了IntoIteratortrait,进一步丰富了集合操作的灵活性。VecDeque::retain_back也被正式加入标准库,为双端队列提供了更高效的反向过滤手段。
升级建议
笔者整理了针对不同类型开发者的升级建议表,大家可以根据自己的项目需求进行对照:
| 目标用户 | 升级时机 | 注意事项 |
|---|---|---|
| 需要与 C 语言可变参数深度交互的开发者 | 建议立即升级 | 新特性能够显著精简 FFI 绑定层的代码。但需要注意 ... 类型(即 VaList)的操作依然属于 unsafe 范畴,必须严格遵循调用约定与安全边界。 |
| 编写底层内存管理和自定义分配器的库作者 | 建议平稳过渡升级 | 务必全面排查代码库中是否存在撤销 Box::leak 的操作模式,请统一替换为更安全的 Box::into_raw 或 Box::into_non_null 接口,以防止触发编译器未定义行为。 |
| 一般应用、Web 服务与后端开发者 | 按常规节奏升级即可 | 执行 $ rustup update stable 即可平滑无缝升级,无需修改现有代码。您可以直接享受新版本带来的编译器底层优化、更快的编译速度和基础库功能扩展。 |