Rust 1.88 终于稳定了裸函数:写汇编不再需要那堆样板代码

Rust 1.88.0 稳定了 #[unsafe(naked)] 属性和 naked_asm! 宏,正式将裸函数(naked functions)带入稳定版本。

从最初的 RFC 提出算起,这个特性等了整整十年。

本文基于 Rust 官方博客 2025 年 7 月 3 日发布的《Stabilizing naked functions》整理撰写,作者为 Folkert de Vries。

原文地址


裸函数是什么

在普通的 Rust 函数中,编译器会在你写的代码前后悄悄插入一些处理逻辑------函数调用约定(calling convention)要求的寄存器保存和恢复、参数传递、返回值处理等等。这些都由编译器自动完成,通常你不需要关心。

但在操作系统内核、编译器内置函数(compiler-builtins)、嵌入式开发等场景里,有时你需要精确控制函数的每一条指令,不能让编译器擅自添加任何东西。这正是裸函数的用途。

顾名思义,"裸"的含义是:编译器不添加任何额外处理,函数体里只有你亲手写的汇编,多一条都没有。

用法示例:

rust 复制代码
/// SAFETY: 遵循 64 位 System-V ABI。
#[unsafe(naked)]
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) -> u64 {
    // 等价于 a.wrapping_add(b)
    core::arch::naked_asm!(
        "lea rax, [rdi + rsi]",
        "ret"
    );
}

函数体内只有一个 naked_asm! 调用,没有其他 Rust 代码。这就是裸函数的全部形态。


为什么不直接用 global_asm!

在裸函数稳定之前,需要在汇编层面精确控制函数的代码通常用 global_asm! 来写。同样的 wrapping_add,用 global_asm! 实现是这样:

rust 复制代码
unsafe extern "sysv64" {
    safe fn wrapping_add(a: u64, b: u64) -> u64
}

core::arch::global_asm!(
    r#"
        .section .text.wrapping_add,"ax",@progbits
        .p2align 2
        .globl wrapping_add
        .type wrapping_add,@function

wrapping_add:
        lea rax, [rdi + rsi]
        ret

.Ltmp0:
        .size wrapping_add, .Ltmp0-wrapping_add
    "#
);

光是定义一个函数,就需要在汇编里手写一堆平台相关的指令(.section.p2align.globl.type.size......)。这些东西是机械性的样板代码,但在不同的目标平台和对象文件格式之间还各有差异,写错了就会出问题。

裸函数会自动生成这些指令,你只需要关注实际的汇编逻辑。

除此之外,global_asm! 还有几个额外的限制:

符号名不参与 Rust 的名称修饰(name mangling) :x86_64 macOS 的符号名会带 _ 前缀,Linux 不带,要做跨平台支持得手动处理这些差异。裸函数的符号名走 Rust 的正常名称修饰流程,不存在这个问题。

全局符号可见性global_asm! 定义的函数符号是全局可见的,可能与其他地方的同名符号冲突。裸函数通过名称修饰天然避免了这个问题。

不支持泛型global_asm! 里没有办法使用泛型,尤其是 const 泛型。裸函数支持泛型,这在某些场景下很有用。

文档与函数定义分离global_asm! 方案需要同时维护 extern 块的声明和 global_asm! 中的实现,安全注释和属性容易因为分散在两处而遗漏或失同步。裸函数把所有东西放在一个定义里。


十年的等待:这个特性的历史

裸函数最早的 RFC 提案可以追溯到 2015 年。

2020 年,随着 Rust 内联汇编语法经历了大规模重新设计,原有的 RFC 被 RFC 2972 取代。新的 RFC 将裸函数的函数体限定为单个汇编调用,并加了一些额外约束。

此后有两个关键的实现变化推动了这个特性走向稳定:

引入独立的 naked_asm! :最初的实现方案是在普通的 asm! 调用上加额外的 lint 检查。这种做法很难给出清晰的错误信息,文档也难以表达。naked_asm! 作为专用宏,行为更容易明确定义------它的语法是 asm!(因为在函数体内)和 global_asm!(只接受部分操作数类型)的混合体。

实现方式从依赖 LLVM 改为降级到全局汇编:早期的实现依赖 LLVM 的 naked 属性来做代码生成。问题是 LLVM 有时会在用户写的指令之外插入额外指令,而且 Rust 现在已经有了非 LLVM 的代码生成后端(比如 GCC 后端、Cranelift),它们需要自己实现一套 LLVM 的未规范化行为。现在的实现方案是把裸函数在编译器内部转换为全局汇编,所有代码生成后端都已经支持输出全局汇编,这同时也保证了最终输出的函数体里只有用户写的指令,一条不多。


一个需要特别注意的安全问题

naked 属性标记为 unsafe,这不是形式上的谨慎,而是有实际原因的。

裸函数的调用约定(ABI)、函数签名、以及汇编实现三者必须完全一致,完全由程序员自己保证。编译器不会插入任何辅助代码,一旦不一致,就可能产生难以调试的内存错误或未定义行为。

比如在上面的例子里,extern "sysv64" 说明这个函数遵循 64 位 System-V ABI,那汇编实现就必须按照这个 ABI 来处理参数和返回值------参数通过 rdirsi 传入,返回值放在 rax。写汇编的人需要自己确认这些约定是对的。

正确的安全注释在裸函数里是必要的,不是可选的。


接下来:两个正在开发的相关特性

裸函数稳定了,但 Rust 汇编生态的改进还在继续推进。有两个实验性特性值得关注。

extern "custom" 函数

裸函数通常搭配 extern "C" 调用约定使用,但在很多实际场景里,这其实是一个谎言------这些函数根本没有遵循任何 Rust 已知的 ABI,而是使用某种只属于该函数自身的自定义约定。

正在开发中的 abi_custom 特性引入了 extern "custom" 调用约定。一个来自 compiler-builtins 的真实例子:

rust 复制代码
#![feature(abi_custom)]

/// 使用 Arm 非标准 ABI 执行除法和取模运算。
#[unsafe(naked)]
pub unsafe extern "custom" fn __aeabi_idivmod() {
    core::arch::naked_asm!(
        "push {{r0, r1, r4, lr}}",
        "bl {trampoline}",
        "pop {{r1, r2}}",
        "muls r2, r2, r0",
        "subs r1, r1, r2",
        "pop {{r4, pc}}",
        trampoline = sym crate::arm::__aeabi_idiv,
    );
}

extern "custom" 有一个重要的语义:用这种约定声明的函数不能被 Rust 代码直接调用,只能通过内联汇编来执行。这是因为编译器不知道如何为一个未知 ABI 的函数生成调用代码,与其让错误静默地发生,不如在编译期报错。

汇编块里的 cfg 条件编译

cfg_asm 特性允许在汇编块的单行上使用 #[cfg(...)] 条件编译,而不需要为了一个条件分支就把整个汇编块复制一遍。

rust 复制代码
#![feature(cfg_asm)]

global_asm!(
    // ...
    #[cfg(feature = "set-sp")]
    "ldr r0, =_stack_start
     msr msp, r0",
    // ...
)

这个功能来自嵌入式开发的实际需求。以 cortex-m crate 为例,它现在不得不写一个自定义宏,每次用到条件编译时都把整块汇编复制一份------有了 cfg_asm,这种绕路就不再必要了。


小结

裸函数的稳定化,对于写操作系统、编译器工具链或嵌入式固件的 Rust 开发者来说是一个值得关注的改动。它不创造新的能力------global_asm! 一直都能做同样的事------但它显著降低了这类代码的书写复杂度,同时消除了一类平台相关的手工处理。

等了十年,从 RFC 到稳定版本,有时候事情就是需要这么久才能做对。


相关推荐
Zwarwolf6 小时前
Rust零散知识点项目汇总
开发语言·rust
X档案库8 小时前
【开源】我做了一套可以 AI 托管的 Markdown 博客与知识库
rust·博客·markdown·marksharex
学逆向的10 小时前
汇编——寻址方式
开发语言·汇编·网络安全
我不是QI11 小时前
Master PDF Editor 逆向复现
汇编·安全·pdf
程序员爱钓鱼13 小时前
Rust Vec 动态数组详解:创建、增删、遍历与排序
前端·后端·rust
_abab13 小时前
Rust重塑系统编程:从Linux内核到AI推理引擎的2026全景解析
linux·人工智能·rust
doiito1 天前
【Agent Harness】Gliding Horse 高级认知优化:让 Agent 的“大脑”和“免疫系统”真正协同
ai·rust·架构设计·系统设计·ai agent
longxiangam2 天前
第 4 篇:Embassy 异步运行时与事件驱动架构
单片机·嵌入式硬件·rust
程序员爱钓鱼2 天前
Rust String 与 &str 详解:字符串所有权、借用与转换
前端·后端·rust
段一凡-华北理工大学2 天前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化