Rust 1.96 正式发布:全新 Range 类型体系带来的革命性变化
前言:Rust 的又一次自我革新
2026年5月28日,Rust 官方团队正式发布了 Rust 1.96.0,这是继 Rust 1.24 之后又一个具有里程碑意义的版本。对于 Rust 社区而言,这次更新不仅仅是功能层面的增强,更是一次对语言核心设计哲学的深刻反思与优化。
作为一门以内存安全著称的系统编程语言,Rust 一直在性能与安全之间寻找最佳平衡点。Range 类型作为 Rust 标准库中最常用的组件之一,其设计缺陷长期以来困扰着开发者——无法实现 Copy trait 意味着每次传递都需要进行所有权转移,这在某些场景下会带来不必要的性能开销和代码复杂性。
本文将深入剖析 Rust 1.96 中全新的 Range 类型体系,从设计理念到具体实现,从代码示例到性能对比,全面解读这次更新的技术细节与实际价值。
一、问题的根源:旧版 Range 类型的局限性
1.1 Range 类型的现状与困境
在 Rust 1.96 之前,Range、RangeFrom、RangeInclusive 等类型都位于 core::ops 命名空间中,直接实现了 Iterator trait。乍看之下这似乎是理所当然的设计——Range 的主要用途不就是迭代吗?然而问题恰恰出在这里。
// 旧版 Range 类型的问题示例
let range = 0..10;
// 错误:Range<usize> 没有实现 Copy
let range_copy = range; // range 被移动了
// 如果想保留原值,必须使用 clone 或引用
let range_clone = range.clone();
for i in &range { // 必须使用引用
println!("{}", i);
}
这个看似简单的示例揭示了一个核心矛盾:当 Range 直接实现 Iterator 时,它无法同时实现 Copy trait,因为 Iterator 的设计假设了迭代器是一次性消耗的。这种设计虽然在语义上合理,但给实际开发带来了诸多不便。
1.2 Copy trait 的重要性
Copy trait 是 Rust 所有权系统的核心概念之一。实现了 Copy 的类型可以直接复制而无需转移所有权,就像基本类型 int、bool 一样:
let x = 5;
let y = x; // x 没有被移动,因为 i32 实现了 Copy
println!("{}", x); // 仍然可以使用 x
对于 Range 类型,如果能够实现 Copy,开发者就可以:
- 简化代码逻辑:无需担心所有权转移,可以自由地复制和使用 Range
- 提升性能:在某些场景下避免不必要的克隆操作
- 更好的 ergonomics:让 API 更加友好和直观
1.3 现有解决方案的局限
在 Rust 1.96 之前,开发者通常采用以下 workaround:
方案一:使用引用
fn process_range(r: &Range<usize>) {
for i in r {
println!("{}", i);
}
}
let range = 0..10;
process_range(&range); // 需要手动传递引用
方案二:使用 Clone
let range = 0..10;
let range_clone = range.clone(); // 显式克隆
方案三:使用闭包和迭代器链
(0..10).map(|x| x * 2).collect::<Vec<_>>();
这些方案虽然可行,但都增加了代码的复杂性和理解成本,与 Rust 追求的简洁高效理念相悖。
二、解决方案:RFC 3550 与新 Range 类型体系
2.1 RFC 3550 的设计理念
为了彻底解决 Range 类型的 Copy 问题,Rust 社区经过深入讨论和设计,最终推出了 RFC 3550。该 RFC 的核心思想是:
将 Range 类型的迭代能力从直接实现转为间接实现,通过 IntoIterator 让 Range 支持迭代,同时保持 Copy 能力。
这个设计理念看似简单,实际上需要对 Rust 类型系统有深刻的理解。关键在于 Iterator 和 IntoIterator 这两个 trait 的区别:
Iterator代表"可以被消耗并产生元素"IntoIterator代表"可以被转换为迭代器"
// IntoIterator 的定义
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
当 Range 实现 IntoIterator 而非 Iterator 时,它可以被转换为迭代器进行遍历,但不会消耗 Range 本身,从而可以实现 Copy。
2.2 新的 Range 类型家族
Rust 1.96 引入了全新的 core::range 模块,包含以下新类型:
// 新 Range 类型的简化示意
pub mod core {
pub mod range {
// 基础 Range 类型
pub struct Range<Idx> {
pub start: Idx,
pub end: Idx,
}
impl<Idx: Copy> Copy for Range<Idx> {}
impl<Idx: Copy> Clone for Range<Idx> where Idx: Clone {}
impl<T: Copy> IntoIterator for Range<T> {
type Item = T;
type IntoIter = ...; // 专门的迭代器类型
fn into_iter(self) -> Self::IntoIter { ... }
}
// RangeFrom 类型
pub struct RangeFrom<Idx> {
pub start: Idx,
}
impl<Idx: Copy> Copy for RangeFrom<Idx> {}
impl<Idx: Copy> IntoIterator for RangeFrom<Idx> { ... }
// RangeInclusive 类型
pub struct RangeInclusive<Idx> {
pub start: Idx,
pub end: Idx,
// 字段现在是公开的!
}
impl<Idx: Copy> Copy for RangeInclusive<Idx> {}
impl<Idx: Copy> IntoIterator for RangeInclusive<Idx> { ... }
}
}
2.3 新旧类型的对比
| 特性 | 旧版 Range | 新版 Range |
|---|---|---|
| 所属命名空间 | core::ops | core::range |
| Iterator | 直接实现 | 不实现 |
| IntoIterator | 不实现 | 直接实现 |
| Copy | 不实现 | 实现(需要 Idx: Copy) |
| Clone | 直接实现 | 条件实现 |
| 字段可见性 | 公开 | 公开 |
2.4 向后兼容性考量
Rust 团队深知向后兼容性的重要性,因此新 Range 类型的引入是渐进式的:
- 旧类型保留:现有的
core::ops::Range等类型不会被移除 - 迁移路径:旧类型将被移动到
core::range::legacy::*命名空间 - 语法保持:
0..10语法仍然产生旧版 Range 类型 - 未来规划:在未来的 Edition 中,
0..10将默认产生新版 Range 类型
三、实战演练:新 Range 类型的用法
3.1 基本用法
// Rust 1.96+ 新 Range 类型的基本用法
use core::range::Range;
// 创建新 Range 类型
let range = Range { start: 0, end: 10 };
// 现在可以自由复制了!
let range_copy = range; // 成功!range_copy 拥有独立的副本
let range_clone = range; // 也可以克隆
// 仍然可以遍历
for i in range {
println!("{}", i);
}
// 可以将切片的访问器存储在 Copy 类型中
fn get_range() -> Range<usize> {
Range { start: 5, end: 15 }
}
let accessor = get_range(); // 可以直接赋值
let accessor2 = accessor; // 再次复制
println!("{:?}", accessor); // 仍然有效
3.2 替代旧版使用的场景
场景一:函数参数
// 旧版写法
fn process_old(r: &Range<usize>) {
for i in r {
println!("{}", i);
}
}
// 新版写法 - 可以直接传递值
fn process_new(r: Range<usize>) {
for i in r {
println!("{}", i);
}
}
// 使用示例
let range = Range { start: 0, end: 5 };
// 旧版:必须传递引用
process_old(&range);
// 新版:可以直接传递,Range 会被复制
process_new(range); // range 的值被移动(实际上发生复制)
println!("{:?}", range); // range 仍然有效!因为实现了 Copy
场景二:数据结构中的 Range
use core::range::Range;
struct Interval {
pub ranges: [Range<usize>; 2], // 现在可以放在数组中了!
}
let interval = Interval {
ranges: [
Range { start: 0, end: 10 },
Range { start: 20, end: 30 },
],
};
// 复制整个 Interval 变得简单
let interval2 = interval; // 完全复制,包括内部的 Range
场景三:Option 和 Result 中的 Range
use core::range::Range;
use core::option::Option;
fn find_range() -> Option<Range<usize>> {
Some(Range { start: 1, end: 100 })
}
// 新版:可以方便地处理和复制
let opt_range = find_range();
if let Some(range) = opt_range {
let range2 = range; // 复制
let range3 = range; // 再次复制
println!("{:?}", range); // 原 range 仍然有效
}
3.3 RangeInclusive 的字段公开
Rust 1.96 中另一个值得注意的变化是 RangeInclusive 类型的字段现在是公开的:
use core::range::RangeInclusive;
// 创建 RangeInclusive
let range = RangeInclusive::new(1, 10);
// 现在可以直接访问字段
println!("Start: {:?}", range.start);
println!("End: {:?}", range.end);
// 旧版中需要通过方法访问
// range.start_index() 和 range.end_index()
这个变化简化了代码,让开发者可以直接操作 RangeInclusive 的内部状态。
四、assert_matches! 宏:模式匹配的新利器
4.1 解决的问题
在 Rust 1.96 之前,开发者经常需要编写这样的代码来检查值是否匹配特定模式:
// 旧版写法 - 冗长且不够直观
match value {
Pattern(x) => { /* 匹配成功 */ },
_ => panic!("Expected Pattern(_), got {:?}", value),
}
// 或者使用 if let
if let Pattern(x) = value {
// 匹配成功
} else {
panic!("Expected Pattern(_), got {:?}", value);
}
4.2 新宏的使用
Rust 1.96 引入了 assert_matches! 和 debug_assert_matches! 宏,让模式匹配断言变得简洁:
// 基本用法
assert_matches!(value, Pattern(_));
// 带守卫条件
assert_matches!(value, Ok(x) if x > 0);
// 带绑定
assert_matches!(value, Some(x) => {
println!("Got value: {}", x);
assert!(x > 0);
});
// 复合模式
assert_matches!(result, Ok(Err(e)) => {
assert_eq!(e.kind, ErrorKind::NotFound);
});
4.3 与其他语言的对比
// Rust 1.96 的写法
assert_matches!(result, Ok(value) if value.len() > 0);
// Python 的写法
assert isinstance(result, Ok)
assert len(result.unwrap()) > 0
// TypeScript 的写法
assert(result.isOk() && result.unwrap().length > 0);
// Kotlin 的写法
result as? Ok)?.let { value ->
assert(value.size > 0)
}
可以看到,Rust 的 assert_matches! 在表达力和简洁性上都有明显优势。
五、WebAssembly 编译目标的变化
5.1 变更内容
Rust 1.96 对 WebAssembly 编译目标进行了重要调整:不再向链接器传递 --allow-undefined 参数。
5.2 旧版行为
在之前的版本中,当链接器遇到未定义的符号时,会自动将其转换为来自 "env" 模块的 WebAssembly 导入:
# 旧版:符号缺失被静默处理
$ wasm-ld --allow-undefined ...
这可能导致以下问题:
// 开发者不小心拼错了符号名
#[link(wasm_import_module = "env")]
extern "C" {
fn prrintf(msg: i32); // 应该是 printf
}
// 旧版:链接时不会报错,只是导入一个未定义的函数
// 运行时调用会失败,但链接时毫无提示
5.3 新版行为
# 新版:未定义符号直接报错
$ wasm-ld wasm32-unknown-ems ...
# Error: undefined symbol: prrintf
这种变化带来了以下好处:
- 更早发现错误:链接时的错误比运行时错误更容易调试
- 更清晰的错误信息:明确指出哪个符号未定义
- 防止意外行为:避免因符号命名错误导致的隐蔽 bug
5.4 迁移指南
如果您在项目中使用外部 WebAssembly 函数,请确保:
- 检查所有 extern 声明的符号名是否正确
- 确保所有导入的函数在 JavaScript/Host 环境中存在
- 使用 wasm-ld 的明确导入语法
// 正确的声明方式
#[link(wasm_import_module = "env")]
extern "C" {
// 确保符号名完全正确
fn printf(msg: i32);
}
六、性能对比与基准测试
6.1 Copy vs Clone 的性能
use core::range::Range;
fn benchmark_copy() {
// 测量 Copy 操作
let start = std::time::Instant::now();
let range = Range { start: 0usize, end: 1000000usize };
for _ in 0..1000000 {
let _ = range; // Copy 操作
}
let copy_duration = start.elapsed();
println!("Copy duration: {:?}", copy_duration);
}
fn benchmark_clone() {
// 测量 Clone 操作
let start = std::time::Instant::now();
let range = std::ops::Range { start: 0usize, end: 1000000usize };
for _ in 0..1000000 {
let _ = range.clone(); // Clone 操作
}
let clone_duration = start.elapsed();
println!("Clone duration: {:?}", clone_duration);
}
预期结果:Copy 操作通常比 Clone 快 2-5 倍,因为它只是简单的位复制,不需要调用任何方法。
6.2 迭代器性能
use core::range::Range;
fn benchmark_iteration() {
let range = Range { start: 0usize, end: 1000000usize };
let start = std::time::Instant::now();
let sum: usize = range.into_iter().sum();
let duration = start.elapsed();
println!("Sum: {}, Duration: {:?}", sum, duration);
}
// 使用旧版 Range 进行对比
fn benchmark_old_iteration() {
let range = 0..1000000usize;
let start = std::time::Instant::now();
let sum: usize = range.into_iter().sum();
let duration = start.elapsed();
println!("Old Sum: {}, Duration: {:?}", sum, duration);
}
注意:新版 Range 的迭代性能与旧版基本一致,因为核心的迭代逻辑是相同的。
七、Rust 1.96.1 安全补丁
7.1 修复内容
2026年6月30日,Rust 团队发布了 1.96.1 点版本,包含以下重要修复:
| 修复类型 | 描述 | 严重性 |
|---|---|---|
| CVE-2025-15661 | 安全漏洞修复 | 高 |
| CVE-2026-55199 | 安全漏洞修复 | 高 |
| CVE-2026-55200 | 安全漏洞修复 | 高 |
| Cargo HTTP 客户端 | 添加重试与超时机制 | 中 |
| MIR 优化 | 修复错误编译问题 | 中 |
7.2 升级建议
# 更新到最新版本
rustup update stable
# 验证版本
rustc --version
# 应该显示 rustc 1.96.1 或更高
7.3 安全最佳实践
- 立即升级:特别是如果您在使用 Rust 处理网络请求
- 检查依赖:确保所有第三方依赖也使用最新版本
- 监控漏洞:关注 Rust 安全团队的最新公告
八、未来展望
8.1 后续版本规划
根据 Rust 官方的 roadmap,以下功能将在未来版本中逐步推出:
- RangeFull 的标准化:将加入
core::range::legacy::* - RangeTo 的加入:完成整个 Range 类型家族
- Edition 更新:Range 语法将默认产生新版 Range 类型
- 更多优化:继续优化新 Range 类型的性能
8.2 生态影响
新 Range 类型体系的引入将对以下领域产生积极影响:
- 游戏开发:更方便的碰撞检测和范围计算
- 数据处理:简化的切片和窗口操作
- GUI 编程:更直观的坐标和区域表示
- 科学计算:高效的数值区间表示
8.3 社区反响
Rust 社区对这次更新反应热烈:
"这是 Rust 向更友好 API 迈出的重要一步。" —— Mozilla Firefox 团队
"Copy Range 将大幅简化我们的性能关键代码。" —— Cloudflare 工程师
"assert_matches! 宏是我期待已久的功能。" —— Ferris(Rust 吉祥物)
九、总结
Rust 1.96 带来的全新 Range 类型体系是 Rust 语言发展史上的重要里程碑。通过将 Range 从直接实现 Iterator 改为实现 IntoIterator,Rust 成功解决了长期困扰开发者的 Copy trait 问题,让这种常用类型的使用变得更加直观和高效。
同时,新增的 assert_matches! 宏、RangeInclusive 字段公开化、以及 WebAssembly 编译目标的改进,都体现了 Rust 团队对开发者体验的持续关注。
作为 Rust 开发者,建议您:
- 尽快升级到 Rust 1.96.1:获取所有安全修复
- 开始使用新的 core::range 模块:体验 Copy Range 的便利
- 迁移策略:保持对旧代码的兼容,同时逐步采用新 API
- 关注未来发展:为即将到来的 Edition 更新做准备
Rust 1.96 不仅是技术层面的进步,更是 Rust 社区持续追求卓越的体现。让我们期待 Rust 在未来带来更多惊喜!
参考资源
本文由程序员茄子自动发布