编程 Rust 1.96 正式发布:可复制 Range 类型、Cargo 双源依赖、Wasm 安全加固——系统编程语言的又一次实用主义进化

2026-07-05 07:44:50 +0800 CST views 18

Rust 1.96 正式发布:可复制 Range 类型、Cargo 双源依赖、Wasm 安全加固——系统编程语言的又一次实用主义进化

从 Range 可 Copy 到 Cargo 双源依赖,Rust 1.96 用三个关键更新,彻底解决开发者的日常痛点。这不是一次炫技式的版本升级,而是一场针对实际工程问题的精准打击。

写在前面:为什么这次更新值得关注?

2026年7月4日,Rust 团队发布了 1.96.0 稳定版。如果你习惯了 Rust 每6周一次的版本节奏,可能会觉得“又是一次小版本更新”。但这次不一样——它带来的三个核心特性,直接击中了 Rust 开发者的日常痛点:

  1. Range 类型终于可 Copy 了:困扰开发者多年的问题,终于有了优雅的解决方案
  2. Cargo 支持双源依赖:本地调试用 Git,发布用 registry,一套配置搞定
  3. Wasm 链接规则严格化:在编译期就拦截配置错误,而不是等到运行时才发现

更关键的是,这次更新体现了 Rust 团队的实用主义哲学:不追求大而全的特性堆砌,而是精准解决开发者在真实项目中遇到的具体问题


一、Range 可 Copy:一个困扰 Rust 社区多年的设计困境

1.1 问题的根源:为什么旧版 Range 不能 Copy?

在 Rust 1.96 之前,我们日常写的 0..10 这样的语法,生成的是 std::ops::Range 类型。这个类型有一个让人头疼的限制:它不能实现 Copy trait

为什么?因为在 Rust 的设计规范中,迭代器(Iterator)是独占、一次性消费的资源。标准库直接为 Range 实现了 Iterator trait,而 Rust 的规则是:实现 Iterator 的类型不能同时实现 Copy

这个设计背后的逻辑是:

// 旧版 Range 的行为
let range = 0..10;
let copy = range; // 这实际上是 move,而不是 copy!

// 为什么?因为 range 本身就是一个迭代器
for i in range {
    println!("{}", i);
}
// 此时 range 已经被消费,无法再次使用

迭代器代表了一个“正在进行的遍历过程”,它维护了当前遍历到的位置。如果允许 Copy,就会出现两个迭代器同时遍历同一个序列的情况——这违背了迭代器的语义。

1.2 实际痛点:当代码变得冗余

这个限制在实际开发中会带来很多不便。最典型的场景是:你想把一个切片访问器存储在结构体中

假设你在开发一个文本处理工具,需要存储“从哪到哪”的切片范围:

// 旧版本(Rust 1.95 及之前)
pub struct Span {
    start: usize,
    end: usize,  // 不得不拆分成两个字段
}

impl Span {
    pub fn of(&self, s: &str) -> &str {
        &s[self.start..self.end]  // 使用时再拼接
    }
}

为什么不能直接存 Range<usize>?因为 Range<usize> 没有实现 Copy,而你的 Span 结构体需要 Copy 语义(可能要放进 Cell、存在栈上、或者作为函数返回值)。

这就导致了一个非常不优雅的结果:明明是一个“区间”的概念,却被迫拆成两个字段来存储

1.3 RFC 3550 的解决方案:新 Range 类型

Rust 1.96 正式稳定了 RFC 3550,提出了一套全新的方案:

核心思路:让 Range 类型“不再是迭代器本身”,而是“可以被转换为迭代器的数据容器”。

新版本引入了独立的 core::range 系列类型:

// 新的 Range 类型(Rust 1.96+)
use core::range::Range;

#[derive(Clone, Copy)]  // 现在可以直接 Copy!
pub struct Span(Range<usize>);

impl Span {
    pub fn of(self, s: &str) -> &str {
        &s[self.0]  // 直接使用,代码更简洁
    }
}

为什么新 Range 可以 Copy?

因为它不再直接实现 Iterator,而是实现 IntoIterator

// 新 Range 类型的设计
pub struct Range<Idx> {
    pub start: Idx,
    pub end: Idx,
}

// 实现 IntoIterator,而不是 Iterator
impl<Idx> IntoIterator for Range<Idx> {
    type Item = Idx;
    type IntoIter = RangeIter<Idx>;  // 迭代器是一个独立的类型
    
    fn into_iter(self) -> Self::IntoIter {
        RangeIter::new(self.start, self.end)
    }
}

// 因此可以实现 Copy
impl<Idx: Copy> Copy for Range<Idx> {}

关键区别

特性旧 Range (std::ops::Range)新 Range (core::range::Range)
实现 Iterator
实现 IntoIterator
实现 Copy
实现 Clone✗(部分情况)
字段可见性私有公开

1.4 实战示例:代码变得多么优雅

让我们看一个实际的例子。假设你在开发一个 JSON 解析器,需要存储 token 的位置信息:

// ====== 旧版本写法(Rust 1.95 及之前)======
use std::ops::Range as OldRange;

#[derive(Debug)]
pub struct Token {
    kind: TokenKind,
    start: usize,   // 不得不拆分
    end: usize,     // 冗余的字段
}

impl Token {
    pub fn new(kind: TokenKind, start: usize, end: usize) -> Self {
        Self { kind, start, end }
    }
    
    pub fn span(&self) -> OldRange<usize> {
        self.start..self.end  // 每次都要拼接
    }
}

// ====== 新版本写法(Rust 1.96+)======
use core::range::Range;

#[derive(Debug, Clone, Copy)]  // 现在可以直接 Copy
pub struct Token {
    kind: TokenKind,
    span: Range<usize>,  // 一个字段搞定
}

impl Token {
    pub fn new(kind: TokenKind, start: usize, end: usize) -> Self {
        Self {
            kind,
            span: Range { start, end },
        }
    }
    
    pub fn text<'a>(&self, source: &'a str) -> &'a str {
        &source[self.span]  // 直接使用
    }
}

1.5 迁移路径:旧代码怎么办?

Rust 团队非常注重向后兼容性。目前,0..10 这样的语法仍然产生旧版 std::ops::Range 类型,而不是新的 core::range::Range

未来的计划是:

  1. 当前阶段:新旧类型并存,旧语法仍产生旧类型
  2. 下一阶段:引入 core::range::legacy::* 作为旧类型的新命名空间
  3. Edition 升级:在未来某个 Edition(可能是 Rust 2027)中,0..10 语法将产生新的 core::range::Range 类型

迁移建议

// 如果你想立即使用新类型
use core::range::Range;

let range = Range { start: 0, end: 10 };

// 如果你需要旧类型(临时兼容)
use std::ops::Range as LegacyRange;

let legacy = 0..10;  // 当前仍然是旧类型

1.6 新 Range 的其他改进

除了支持 Copy,新的 Range 类型还有其他改进:

字段公开

// 旧版 RangeInclusive 的字段是私有的
let range = 0..=10;
// range.start  // 编译错误:字段私有

// 新版 RangeInclusive 的字段是公开的
use core::range::RangeInclusive;
let range = RangeInclusive { start: 0, end: 10 };
println!("start: {}, end: {}", range.start, range.end);  // OK

更好的 API 设计

use core::range::{Range, RangeFrom, RangeInclusive, RangeTo};

// 各种 Range 变体
let range = Range { start: 0, end: 10 };          // 0..10
let range_from = RangeFrom { start: 0 };          // 0..
let range_inclusive = RangeInclusive { start: 0, end: 10 };  // 0..=10
let range_to = RangeTo { end: 10 };               // ..10

// 所有这些类型都支持 Copy
let copy = range;
let another = range;  // 编译通过!

二、Cargo 双源依赖:本地调试与发布配置的终极解决方案

2.1 痛点场景:开发与发布的依赖冲突

这是困扰 Rust 团队多年的一个实际问题。假设你的团队维护一个内部公共工具库 my-utils

  • 开发阶段:你需要依赖 Git 仓库的最新代码,修复 bug、测试新特性
  • 发布阶段:必须使用私有 registry 或 crates.io 上的稳定版本

在 Rust 1.96 之前,你只能二选一:

# 方案 A:依赖 Git 源(适合开发,不适合发布)
[dependencies]
my-utils = { git = "https://github.com/xxx/my-utils.git", branch = "main" }

# 方案 B:依赖 registry(适合发布,不适合开发调试)
[dependencies]
my-utils = { version = "1.2.0", registry = "my-private-registry" }

过去的痛苦流程

  1. 开发时,手动改成 Git 源
  2. 提交前,改回 registry 版本
  3. 忘记改回来 → CI 构建失败
  4. 团队协作,每个人都在重复这个过程...

2.2 Rust 1.96 的解决方案:单依赖双源配置

Rust 1.96 给出了优雅的解决方案:一个依赖项同时指定两个来源,根据构建场景自动切换

# Cargo.toml
[dependencies]
my-utils = { 
    git = "https://github.com/xxx/my-utils.git", 
    registry = "my-private-registry" 
}

切换规则

场景使用的源
本地开发 (cargo build)Git 源
运行测试 (cargo test)Git 源
发布构建 (cargo publish)Registry 源
CI 构建(非发布)Git 源
--registry 参数指定Registry 源

2.3 实战示例:完整的配置流程

步骤 1:配置私有 registry

# .cargo/config.toml
[registries]
my-private-registry = { index = "https://github.com/xxx/crates-index" }

步骤 2:在 Cargo.toml 中配置双源依赖

[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[dependencies]
# 双源依赖:Git + Registry
my-utils = { 
    git = "https://github.com/company/my-utils.git", 
    branch = "develop",  # 可选:指定分支
    registry = "my-private-registry",
    version = "1.2.0"    # 可选:指定 registry 版本
}

# 也可以省略 version,自动使用 registry 上的最新版本
my-another-lib = {
    git = "https://github.com/company/my-another-lib.git",
    registry = "my-private-registry"
}

步骤 3:验证配置

# 本地开发:使用 Git 源
cargo build
# 输出:Downloading my-utils from git repository...

# 发布:使用 Registry 源
cargo publish --registry my-private-registry
# 输出:Using my-utils from registry my-private-registry...

2.4 高级用法:结合 patchreplace

Cargo 双源依赖还可以与其他功能结合使用:

场景:临时使用本地修改的库

# Cargo.toml
[dependencies]
my-utils = { 
    git = "https://github.com/company/my-utils.git", 
    registry = "my-private-registry" 
}

# 临时 patch 到本地路径
[patch."https://github.com/company/my-utils.git"]
my-utils = { path = "../my-utils-local" }

这样,本地开发时会使用 ../my-utils-local,而发布时自动使用 registry 版本。

2.5 为什么这个功能如此重要?

这不是一个“锦上添花”的功能,而是解决真实工作流程痛点的实用性改进

  1. 消除人工操作:不再需要手动切换依赖配置
  2. 防止发布事故:避免忘记改回 registry 版本导致的 CI 失败
  3. 团队协作友好:统一的配置,每个人都能遵循相同的流程
  4. CI/CD 集成:构建脚本不需要特殊处理,cargo publish 自动使用 registry

三、Wasm 链接规则严格化:从运行时错误到编译期错误

3.1 背景:WebAssembly 的未定义符号问题

在 Rust 1.96 之前,编译 WebAssembly 目标时,Rust 默认会向链接器传递 --allow-undefined 参数。这意味着:

如果有未定义的符号,链接器不会报错,而是将其自动转换为从 env 模块的导入

这看起来很“宽容”,但实际上是一个隐患:

// 假设你写了一个 Wasm 函数
#[link(wasm_import_module = "env")]
extern "C" {
    fn my_import_function() -> i32;
}

pub fn call_external() -> i32 {
    unsafe { my_import_function() }
}

如果你不小心写错了函数名:

// 拼写错误:my_import_functon(少了一个 i)
extern "C" {
    fn my_import_functon() -> i32;
}

旧版本的行为

编译通过 ✓
链接通过 ✓
生成 .wasm 文件 ✓
运行时错误 ✗  ← 问题在这里!

你会得到一个运行时错误:“import env.my_import_functon not found”。

3.2 Rust 1.96 的改进:编译期拦截

Rust 1.96 彻底移除了 --allow-undefined 的默认行为。现在:

如果有未定义符号,直接在编译期报错

error: linking with `rust-lld` failed
  |
  = note: rust-lld: error: undefined symbol: my_import_functon

这意味着你可以在开发阶段就发现问题,而不是等到部署到生产环境后才发现。

3.3 实战影响:迁移指南

如果你依赖旧行为(极少数情况):

# 显式启用 --allow-undefined
RUSTFLAGS="-C link-arg=--allow-undefined" cargo build --target wasm32-unknown-unknown

最佳实践

// 正确的做法:明确声明所有导入
#[link(wasm_import_module = "env")]
extern "C" {
    // 显式列出所有需要的导入
    fn console_log(ptr: *const u8, len: usize);
    fn get_timestamp() -> u64;
}

// 或者使用 wasm-bindgen
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

3.4 为什么这个改进很重要?

场景旧版本新版本(Rust 1.96+)
拼写错误运行时崩溃编译期报错
符号重命名运行时崩溃编译期报错
配置遗漏运行时崩溃编译期报错

核心收益:将问题从“运行时发现”提前到“编译期发现”,大大降低调试成本。


四、新增便捷断言宏:assert_matches!

4.1 痛点:旧版本的繁琐写法

在 Rust 1.96 之前,如果你想断言一个值匹配某个模式,需要这样写:

enum Status {
    Idle,
    Working(u32),
    Offline,
}

let status = Status::Working(10086);

// 旧写法:嵌套两层宏
assert!(matches!(status, Status::Working(_)));

虽然可行,但有几个问题:

  1. 代码冗余assert! 包裹 matches!,两层嵌套
  2. 错误信息不清晰:断言失败时,只知道 “assertion failed”,不知道实际值是什么
  3. 无法提取匹配值:如果想在断言的同时提取值,需要额外代码

4.2 新版本:assert_matches!

Rust 1.96 新增了 assert_matches!debug_assert_matches! 宏:

use std::assert_matches::assert_matches;

enum Status {
    Idle,
    Working(u32),
    Offline,
}

let status = Status::Working(10086);

// 新写法:直接匹配模式,语义更清晰
assert_matches!(status, Status::Working(_));

// 如果断言失败,会输出 Debug 格式的实际值
// assert_matches!(status, Status::Idle);
// → assertion failed: `status` does not match `Status::Idle`
//   actual: Working(10086)

4.3 高级用法:提取匹配值

use std::assert_matches::assert_matches;

let status = Status::Working(10086);

// 使用 @ 绑定语法提取值
assert_matches!(status, Status::Working(id @ 10086) => {
    println!("Working on order: {}", id);
});

// 或者使用 if let 风格
if let Status::Working(id) = status {
    assert_eq!(id, 10086);
}

4.4 debug_assert_matches!:调试版本专用

use std::assert_matches::debug_assert_matches;

// 只在 debug 模式下检查,release 模式下会被移除
debug_assert_matches!(status, Status::Working(_));

// 等价于
if cfg!(debug_assertions) {
    assert_matches!(status, Status::Working(_));
}

五、安全漏洞修复:CVE-2025-15661、CVE-2026-55199、CVE-2026-55200

Rust 1.96.1 点版本修复了三个安全漏洞,建议所有用户尽快升级。

5.1 CVE-2026-5223(中危):第三方注册表 crate 压缩包软链接篡改

影响范围:仅影响使用第三方私有 registry 的用户,官方 crates.io 用户不受影响。

问题描述:第三方 registry 的 crate 压缩包中的软链接可被利用篡改本地文件。

修复措施:新版本将直接拒绝解析包内软链接。

缓解建议

# 立即升级
rustup update stable

# 检查是否使用了第三方 registry
grep -r "registry" .cargo/config.toml

5.2 CVE-2026-5222(低危):Git 协议注册表 URL 标准化认证漏洞

影响范围:使用 Git 协议的私有 registry 用户。

问题描述:Git 协议注册表 URL 标准化存在认证绕过风险。

修复措施:优化源地址校验逻辑。

5.3 其他修复

  • Cargo HTTP 客户端缺少重试与超时机制:已添加默认重试和超时配置
  • MIR 优化中存在的错误编译问题:已修复

六、完整更新清单:还有什么值得关注?

6.1 标准库 API 稳定

Rust 1.96 稳定了 20+ 新 API,以下是几个实用的:

// slice::array_windows:编译期安全的滑动窗口
let arr = [1, 2, 3, 4, 5];
for window in arr.array_windows::<2>() {
    println!("{:?}", window);  // [1, 2], [2, 3], [3, 4], [4, 5]
}

// f32::fma:融合乘加运算(精度更高)
let a: f32 = 0.1;
let b: f32 = 0.2;
let c: f32 = 0.3;
let result = a.mul_add(b, c);  // a * b + c,精度更高

6.2 编译器改进

  • 增量编译优化:大型项目的增量编译速度提升约 15%
  • 错误信息改进:生命周期错误提示更友好
  • 类型推断增强:复杂泛型场景下的类型推断更准确

6.3 Cargo 改进

# 新增:cargo report future-incompatibilities
# 提前发现未来版本的不兼容变更
cargo report future-incompatibilities

# 新增:cargo tree --duplicates
# 查看依赖树中的重复版本
cargo tree --duplicates

七、总结:实用主义者的胜利

Rust 1.96 不是一次“革命性”的更新,但它是一次“实用性”的胜利。它解决的三个核心问题——Range 可 Copy、Cargo 双源依赖、Wasm 链接严格化——都是开发者在日常工作中反复遇到的真实痛点。

升级建议

# 立即升级
rustup update stable

# 查看当前版本
rustc --version
# rustc 1.96.0 (xxxxxxx 2026-05-28)

# 如果需要使用新 Range 类型
use core::range::{Range, RangeFrom, RangeInclusive};

迁移清单

  • 升级到 Rust 1.96.1(包含安全修复)
  • 检查是否有依赖第三方私有 registry(CVE-2026-5223)
  • 更新 WebAssembly 构建脚本(移除 --allow-undefined 依赖)
  • 尝试新的 assert_matches! 宏替换旧的 assert!(matches!(...))
  • 评估是否需要迁移到新的 core::range 类型

展望

Rust 的版本迭代节奏稳定,每 6 周一个新版本。从这次更新可以看出,Rust 团队非常注重实用性向后兼容性——新特性不会破坏旧代码,而是提供更好的替代方案。

Rust 1.96 的哲学:不追求炫技,只解决实际问题。这正是 Rust 能够在系统编程领域持续增长的原因。


参考资料

复制全文 生成海报 Rust 系统编程 版本更新 Cargo WebAssembly

推荐文章

PHP服务器直传阿里云OSS
2024-11-18 19:04:44 +0800 CST
Vue3中如何进行性能优化?
2024-11-17 22:52:59 +0800 CST
实用MySQL函数
2024-11-19 03:00:12 +0800 CST
Vue3中的v-for指令有什么新特性?
2024-11-18 12:34:09 +0800 CST
阿里云发送短信php
2025-06-16 20:36:07 +0800 CST
JavaScript 流程控制
2024-11-19 05:14:38 +0800 CST
程序员茄子在线接单