老铁们,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_partsVec::into_raw_parts 终于稳定了。

什么意思?StringVec 的内存布局是连续的:指针、长度、容量。传给 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_ifpop_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 的朋友也转发给他们。

有什么问题,评论区聊。