Rust 1.93 发布:musl 升级、汇编条件编译、零拷贝全家桶

老铁们,Rust 1.93 来了
2026年1月22日,Rust 1.93 正式发布。
看到这个版本号你可能会想:“1.92 才发布一个月出头,这次能有啥大变化?”
别说,这次还真有几个改动,用起来特别舒服。就像你常去的早餐店,老板突然换了新的辣椒酱,东西还是那个东西,但味道更对胃口了。
先说怎么升级:
rustup update stable
完事。
为什么这个版本值得关注
一个词形容 Rust 1.93:补漏。
以前用着不得劲的地方,这次给修好了;以前要绕弯的地方,这次给捋直了。说几件事:
所有 *-linux-musl 目标统一升级到 musl 1.2.5,静态链接更靠谱了。汇编里面能用 #[cfg] 做条件编译了,不用复制粘贴一整块 asm 代码。全局分配器可以放心用 thread_local!,这个限制卡了大家好多年。
都是"用的时候疼,不用的时候忘了"的那种改动。咱们一个一个聊。
musl 1.2.5:静态链接的人有福了
做过静态链接的 Rust 二进制,musl 这个词你肯定不眼生。
musl 是一个轻量级的 C 标准库实现,专门用来做静态链接。说白了就是把程序依赖的所有代码都打包进一个二进制文件里面,丢到服务器上直接跑,不用装 libgcc、glibc 这些。
这对于什么场景爽?
容器镜像。你用 Alpine Linux 做过镜像吧?那玩意儿小得可怜,整个系统也就几十兆。但 Alpine 用的是 musl,不是 glibc。你用标准的 Rust 编译出来的二进制丢进去,很可能跑不起来,两边 libc 对不上。
以前你得用 x86_64-unknown-linux-musl 这种 target 来编译,但时不时会遇到一些奇怪的问题,比如 DNS 解析抽风、某些符号找不到。
现在 musl 升级到 1.2.5,这些问题大部分都修复了。尤其是 DNS 相关的 bug,1.2.4 和 1.2.5 下了大功夫。
可能会遇到的小麻烦
musl 1.2.4 移除了一些老旧的 libc 符号。你某个依赖还在用很老的 libc 版本的话,可能会报 “undefined reference” 这种错。
解决方案很简单:
cargo update
运行一下,依赖更新到最新版,一般就解决了。Rust 团队用 crater 跑过测试,breaking 的项目不多,大多是那种好几年没更新过的老项目。
想验证静态构建有没有问题,可以这样玩:
rustup target add x86_64-unknown-linux-musl
RUSTFLAGS="-C target-feature==crt-static" cargo build --target x86_64-unknown-linux-musl --release
如果遇到 undefined reference to 'open64' 这种问题,升级 libc 到 0.2.146 以上版本即可。
汇编里的 #[cfg]:不用复制粘贴了
写内联汇编的时候,你可能遇到过这种需求:根据不同的 CPU 特性,输出不同的汇编指令。比如支持 SSE2 的机器上用向量指令,不支持的就用普通指令。
以前你怎么写?整个 asm 块复制一份,改条件:
// 支持 SSE2 的版本
#[cfg(target_feature = "sse2")]
unsafe {
std::arch::asm!(
"pxor %xmm0, %xmm0",
options(nostack, preserves_flags)
);
}
// 不支持 SSE2 的版本
#[cfg(not(target_feature = "sse2"))]
unsafe {
std::arch::asm!(
"mov $0, %eax",
options(nostack, preserves_flags)
);
}
复制粘贴不说,还容易改漏。如果有十条指令只有一条不一样,你得复制十遍。
现在好了,直接在 asm 里面写 #[cfg]:
use std::arch::asm;
unsafe {
asm!(
"nop",
#[cfg(target_feature = "sse2")]
"pxor %xmm0, %xmm0",
options(nostack, preserves_flags)
);
}
看,就加了一个 #[cfg(target_feature = "sse2")],这一行只在开启 SSE2 的时候才会被编译进去。
清爽多了。以前写 C++ 模板没有 if constexpr 的时候也是这种痛,得写两套函数。条件编译的粒度越细,代码越干净,这道理哪个语言都一样。
谁用得上?
不搞底层开发、操作系统、加密这些领域的话,可能用不上。但正好在这个坑里的人,这功能能省不少力气。
全局分配器与 TLS:多年老债终于还了
这个改动等了很久。
以前你在 Rust 里写自定义的全局分配器,在 alloc 方法里调用 thread_local! 或者 std::thread::current(),编译器直接报错,说"re-entrancy doom-loop"(重入死循环)。
为什么?thread_local! 本身需要分配内存来存储线程局部数据,而你正在实现的恰恰就是全局分配器,这就成了自己调用自己。好比你想用左手把右手举起来。
但很多合理的分配器设计就是需要记录每个线程的分配统计。比如你想知道每个线程用了多少内存,好做性能分析。以前只能绕弯,要么用 C 写分配器,要么上各种 hack。
1.93 把这个问题解决了。标准库内部用系统分配器做 fallback,不会再出现死循环。
看个例子:
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
struct MyAlloc;
thread_local! {
static ALLOCATED: Cell<usize> = Cell::new(0);
}
unsafe impl GlobalAlloc for MyAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let p = System.alloc(layout);
if !p.is_null() {
// 现在可以安全地使用 thread_local! 了
ALLOCATED.with(|c| c.set(c.get() + layout.size()));
}
p
}
}
#[global_allocator]
static A: MyAlloc = MyAlloc;
这段代码在 1.93 之前编译不过,现在没问题了。线程级内存统计、缓存池管理都可以这样搞。
新稳定的 API
这次稳定了一批 API,挑几个实用的说说。
数组和切片的相互转换
Slices ↔ arrays 这个功能写协议解析的时候太好用了。
以前你想把 slice 的前四个字节转成数组,得这样写:
let arr: &[u8; 4] = buf[..4].try_into().unwrap();
现在一行搞定:
let arr = buf.get(..4).and_then(|s| s.as_array());
看这行代码,像不像从快递柜里拿快递?原来你得先把快递从柜子里拿出来,再放进筐子里;现在直接一步到位,柜子变筐子。
零拷贝:into_raw_parts

String::into_raw_parts 和 Vec::into_raw_parts 终于稳定了。
什么意思?String 和 Vec 的内存布局是连续的:指针、长度、容量。传给 C 代码或者做高级内存操作,以前你得用 as_ptr() 和 len() 分别拿这些信息。
现在一行搞定:
let s = String::from("hello");
let (ptr, len, cap) = s.into_raw_parts();
// ... pass to C, or stash ...
// 还能再组装回来
let s2 = unsafe { String::from_raw_parts(ptr, len, cap) };
assert_eq!(s2, "hello");
注意:into_raw_parts 之后,原来的 String 就被你接管了,内存归你管。忘了解放就内存泄漏,放早了就 double-free。
就像你从快递站把包裹取出来自己保管,快递站不管了。你要么好好放着,要么记得还回去。
VecDeque 的条件弹出
VecDeque::pop_front_if 和 pop_back_if 也稳定了。
以前你想弹出一个满足条件的元素,得这样写:
let front = q.front();
if front.map(|&x| x % 2 == 1).unwrap_or(false) {
q.pop_front();
}
现在一行搞定:
q.pop_front_if(|&x| x % 2 == 1);
适合做 TTL 过期删除、去重、合并队列这些场景。
Duration 的纳秒支持
Duration::from_nanos_u128 也来了。
以前你想从很大的纳秒数创建 Duration,得自己拆分:
let nanos = 10_u128.pow(12);
let secs = (nanos / 1_000_000_000) as u64;
let subsec_nanos = (nanos % 1_000_000_000) as u32;
let duration = Duration::new(secs, subsec_nanos);
现在直接来:
use std::time::Duration;
let d = Duration::from_nanos_u128(10_u128.pow(12)); // 1,000,000,000,000ns
超过 Duration::MAX 会 panic,比溢出后得到一个完全错误的时间要好。
升级指南
Rust 1.93 的升级就两步。
更新工具链:
rustup update stable
cargo update
用 musl 的话,验证一下静态构建:
# 添加 musl target
rustup target add x86_64-unknown-linux-musl
# 构建测试
RUSTFLAGS="-C target-feature=+crt-static" cargo build --target x86_64-unknown-linux-musl --release
就这样。
写在最后
Rust 1.93 不是一个让人"哇塞"的大版本,但每个改动都挺实在:
- musl 1.2.5 让静态构建更靠谱
#[cfg]inside asm 干掉了重复代码- 全局分配器能碰 TLS 了
- 零拷贝 API 让 FFI 少绕弯
这种版本,往往说明生态在变成熟。那些用着不得劲的地方,一个一个被磨平了,Rust 写起来越来越顺手。
好了,今天聊到这。升级试试,有问题评论区见。
觉得有用的话,点个赞让更多人看到。身边有写 Rust 的朋友也转发给他们。
有什么问题,评论区聊。