首页
/ Rust E0045 错误详解:变参函数为何只能搭配 C 调用约定 —— 从 rustc 源码看 C-variadic FFI 的 ABI 检查机制

Rust E0045 错误详解:变参函数为何只能搭配 C 调用约定 —— 从 rustc 源码看 C-variadic FFI 的 ABI 检查机制

2026-09-06 19:06:57作者:平淮齐Percy

E0045 是 rustc 在 FFI(Foreign Function Interface)场景中报出的编译错误:变参(variadic)函数只允许使用兼容 C 调用约定的 ABI,而非法地声明在 extern "Rust"(或其他不支持变参的约定)块中。读完本文,你将完整理解该错误的触发条件与官方修复方式,并基于 rustc 源码掌握 check_c_variadic_abi 检查流程、CVariadicStatus 判定逻辑,以及哪些调用约定(C、cdecl、aapcs、Win64、SysV64 等)被允许声明 C 风格变参函数。

1. 错误触发场景与标准修复

E0045 的官方诊断条目位于 E0045.md,其原始描述为:"Variadic parameters have been used on a non-C ABI function."(变参参数被用于一个非 C ABI 的函数上)

1.1 触发错误的错误代码示例

文档给出的错误示例如下——在一个 extern "Rust" 块中声明了带 ... 的变参函数:

extern "Rust" {
    fn foo(x: u8, ...); // error!
}

该声明会触发 E0045。核心原因是:Rust 的 FFI 只支持为与 C 代码互操作而存在的变参参数,因此变参函数必须使用 C ABI。

1.2 官方修复方式

将声明放入 extern "C" 块中即可:

extern "C" {
    fn foo(x: u8, ...);
}

这一规则的本质是:... 在 Rust 中只表示"C 风格的可变参数"(即 stdarg.h 那种由调用方压栈、被调用方通过约定恢复的机制),它并不是一种泛化的、可适配任意调用约定的语法。任何无法像 C 那样完成变参清理的调用约定,都不允许出现 ...

2. 编译器如何判定:check_c_variadic_abi 检查流程

E0045 的报错由 rustc_hir_analysis 中的函数级 ABI 检查函数触发。其完整实现见 check_c_variadic_abi

fn check_c_variadic_abi(tcx: TyCtxt<'_>, decl: &hir::FnDecl<'_>, abi: ExternAbi, span: Span) {
    if !decl.c_variadic() {
        // Not even a variadic function.
        return;
    }

    match abi.supports_c_variadic() {
        CVariadicStatus::Stable => {}
        CVariadicStatus::NotSupported => {
            tcx.dcx()
                .create_err(diagnostics::VariadicFunctionCompatibleConvention {
                    span,
                    convention: &format!("{abi}"),
                })
                .emit();
        }
        CVariadicStatus::Unstable { feature } => {
            if !tcx.features().enabled(feature) {
                feature_err(
                    &tcx.sess,
                    feature,
                    span,
                    format!("C-variadic functions with the {abi} calling convention are unstable"),
                )
                .emit();
            }
        }
    }
}

流程可以归纳为四步:

  1. 非变参函数直接放行:通过 decl.c_variadic() 判断函数签名是否真的带 ...,没有则直接返回;
  2. 查询 ABI 的变参支持状态:调用 abi.supports_c_variadic(),返回三态枚举 CVariadicStatus
  3. NotSupported 分支即 E0045:构造 VariadicFunctionCompatibleConvention 诊断并 emit;
  4. Unstable { feature } 分支:若该 ABI 的 C-variadic 能力挂在某个未启用的 nightly feature 上,则报 feature 未启用错误,而不是 E0045。

对应的诊断结构定义在 rustc_hir_analysis/src/diagnostics.rs

#[derive(Diagnostic)]
#[diag("C-variadic functions with the {$convention} calling convention are not supported", code = E0045)]
pub(crate) struct VariadicFunctionCompatibleConvention<'a> {
    #[primary_span]
    #[label("C-variadic function must have a compatible calling convention")]
    pub span: Span,
    pub convention: &'a str,
}

从源码结构看,当前 rustc 实际输出的错误文本是 "C-variadic functions with the {convention} calling convention are not supported"(其中 {convention} 为具体 ABI 名称,例如 RustStdcall 等),并附带标签 "C-variadic function must have a compatible calling convention"。这与 E0045.md 中较为简略的标题式描述相比,信息更精确——它会直接告诉你是哪一种调用约定不兼容。

2.1 检查的两个入口

check_c_variadic_abi 并非只有一个调用点,rustc 在两处都会执行该检查:

  • 函数项检查check.rs 在处理 extern 函数项(包括 extern 块内的声明)时调用 check_c_variadic_abi(tcx, sig.decl, abi, item.span)
  • 类型/函数项签名 loweringhir_ty_lowering/mod.rs 在处理函数体(bf)的 ABI 时同样调用 check_c_variadic_abi(tcx, bf.decl, bf.abi, hir_ty.span)

两个入口覆盖了"仅声明"与"带函数体/完整签名"两类场景,保证无论变参声明出现在哪种语法位置,都会被统一拦截。

3. CVariadicStatus 与各 ABI 的变参支持矩阵

判定逻辑的底层实现在 rustc_abi 中。枚举定义见 extern_abi.rs

#[derive(Debug)]
pub enum CVariadicStatus {
    NotSupported,
    Stable,
    Unstable { feature: Symbol },
}

核心判定函数是 ExternAbi::supports_c_variadic,其源码注释与匹配分支完整列出了各 ABI 的支持依据:

/// Returns whether the ABI supports C variadics. This only controls whether we allow *imports*
/// of such functions via `extern` blocks and definition via naked functions; there's a
/// separate check during AST construction guarding *definitions* of variadic functions.
#[cfg(feature = "nightly")]
pub fn supports_c_variadic(self) -> CVariadicStatus {
    // * C and Cdecl obviously support varargs.
    // * C can be based on Aapcs, SysV64 or Win64, so they must support varargs.
    // * EfiApi is based on Win64 or C, so it also supports it.
    // * System automatically falls back to C when used with variadics, therefore supports it.
    //
    // * Stdcall does not, because it would be impossible for the callee to clean
    //   up the arguments. (callee doesn't know how many arguments are there)
    // * Same for Fastcall, Vectorcall and Thiscall.
    // * Other calling conventions are related to hardware or the compiler itself.
    //
    // All of the supported ones must have a test in `tests/codegen/cffi/c-variadic-ffi.rs`.
    match self {
        Self::C { .. }
        | Self::Cdecl { .. }
        | Self::Aapcs { .. }
        | Self::Win64 { .. }
        | Self::SysV64 { .. }
        | Self::EfiApi
        | Self::System { .. } => CVariadicStatus::Stable,
        _ => CVariadicStatus::NotSupported,
    }
}

整理成支持矩阵(依据当前仓库源码,均为 Stable 状态):

调用约定(extern "..." C-variadic 状态 源码依据
C 支持(Stable) C 与 C 风格 varargs 天然兼容
Cdecl 支持(Stable) cdecl 本身即 C 默认约定
Aapcs 支持(Stable) C 在 ARM 上可基于 AAPCS
Win64 支持(Stable) C 在 Windows x86_64 上基于 Win64
SysV64 支持(Stable) C 在 Unix x86_64 上基于 SysV64
EfiApi 支持(Stable) 基于 Win64 或 C
System 支持(Stable) 与变参联用时自动回退到 C
其他(如 RustStdcallFastcallVectorcallThiscall 等) 不支持(NotSupported),触发 E0045 见下方原理说明

3.1 为什么 stdcall 等约定不支持变参

源码注释给出了关键理由:

  • stdcall:被调用方负责清理栈(callee-cleanup),但面对变参时被调用方无法知道实际传入了多少参数,因此不可能完成栈清理,语义上不可行;
  • fastcallvectorcallthiscall:同理,这些约定存在寄存器传参、向量寄存器传参等固定规则,无法与"C 变参"的压栈/恢复机制共存;
  • 其他约定(如 Rust 自身的一系列约定)与硬件或编译器内部机制绑定,不在 C FFI 语义之内。

这正是 E0045 存在的原因:编译器必须在使用点拦截这类 ABI 与变参的非法组合,而不是让代码悄悄编译出未定义行为。

3.2 注意:该检查的适用边界

supports_c_variadic 的 doc 注释还明确了检查边界(见 extern_abi.rs):它只控制通过 extern 块导入此类函数、以及通过 naked 函数定义这类函数是否被允许;对于直接定义变参函数(在 Rust 函数签名里写 ...),还存在 AST 构建阶段的另一道独立检查。也就是说,E0045 这条诊断链路聚焦的是 extern 声明场景,而 Rust 侧"根本不能定义 ... 函数"的限制由更早的阶段负责。

4. 实战要点总结

结合官方文档与源码,使用变参 FFI 时的要点如下:

  1. 声明 C 变参函数一律使用 extern "C"(或上表中明确支持变参的约定)。这是 E0045.md 给出的标准修复:
    extern "C" {
        fn foo(x: u8, ...);
    }
    
  2. 看到 E0045 时,按错误信息中的 ABI 名称定位:当前 rustc 的诊断文本会直接给出约定名(如 C-variadic functions with the Rust calling convention are not supported),将其改到兼容约定即可;
  3. 不要依赖"任意 ABI + 变参"的组合:从 supports_c_variadic 的三态设计可以看出,编译器对每个 ABI 都做了显式白名单式判定,未列入 C / Cdecl / Aapcs / Win64 / SysV64 / EfiApi / System 的约定一律 NotSupported
  4. 变参只服务于 C 互操作:在 Rust 侧需要"不定数量参数"时,应使用 Vecslice 等安全抽象;... 是为对接 C 头文件中 printf 类函数准备的 FFI 语法,并非 Rust 语言层面的可变参特性。

5. 相关源码索引

主题 路径
E0045 官方诊断文档 compiler/rustc_error_codes/src/error_codes/E0045.md
check_c_variadic_abi 检查主流程 compiler/rustc_hir_analysis/src/lib.rs
VariadicFunctionCompatibleConvention 诊断定义 compiler/rustc_hir_analysis/src/diagnostics.rs
CVariadicStatussupports_c_variadic 判定 compiler/rustc_abi/src/extern_abi.rs
检查入口之一(函数项检查) compiler/rustc_hir_analysis/src/check/check.rs
检查入口之二(HIR 类型 lowering) compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
登录后查看全文
热门项目推荐
相关项目推荐