Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

外部块

Syntax
ExternBlock
    unsafe?1 extern Abi? {
        InnerAttribute*
        ExternalItem*
    }

ExternalItem
    OuterAttribute* (
        MacroInvocationSemi
      | Visibility? StaticItem
      | Visibility? Function
    )

外部块提供在当前 crate 中未 定义 而是 声明 的 项 的声明,是 Rust 外部函数接口 (FFI) 的基础。这些类似于未经检查的导入。

外部块中允许两种 项 声明函数静态项

仅允许在 unsafe 上下文 中调用在外部块中声明的 不安全 函数或访问 不安全 静态项。

外部块在其所在的模块或块的 值命名空间 中定义其函数和静态项。

从语义上讲,unsafe 关键字必须出现在外部块的 extern 关键字之前。

2024 版次差异

在 2024 版次 之前,unsafe 关键字是可选的。只有当外部块本身被标记为 unsafe 时,才允许使用 safeunsafe 项限定符。

函数

外部块中的函数声明方式与其它 Rust 函数相同,不同之处在于它们不能有函数体,而是以分号结尾。

参数中不允许使用模式,只能使用 IDENTIFIER_

允许使用 safeunsafe 函数限定符,但不允许使用其它函数限定符(例如 constasyncextern)。

外部块中的函数可以被 Rust 代码调用,就像在 Rust 中定义的函数一样。Rust 编译器会自动在 Rust ABI 和外部 ABI 之间进行转换。

在 extern 块中声明的函数隐式地是 unsafe 的,除非存在 safe 函数限定符。

当强制转换为函数指针时,在 extern 块中声明的函数具有类型 extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R,其中 'l1、… 'lm 是其生命周期参数,A1、…、An 是其参数的声明类型,而 R 是声明的返回类型。

静态项

外部块中的 静态项 的声明方式与外部块之外的 静态项 相同,不同之处在于它们没有初始化其值的表达式。

除非在 extern 块中声明的 静态项 被限定为 safe,否则访问该 项 是 unsafe 的,无论它是否可变,因为由于某些任意(例如 C)代码负责初始化该 静态项,因此没有任何东西能保证该 静态项 内存处的位模式对于声明它的类型是有效的。

外部 静态项 既可以是不可变的,也可以是可变的,就像外部块之外的 静态项 一样。

在执行任何 Rust 代码之前, 必须 初始化不可变 静态项。仅在 Rust 代码从中读取之前初始化 静态项 是不够的。一旦 Rust 代码运行,修改不可变 静态项(从 Rust 内部或外部)就是 未定义行为 (UB),除非修改发生在 UnsafeCell 内部的字节上。

ABI

extern 关键字后面可以跟一个可选的 ABI 字符串。ABI 指定了块中函数的调用约定。调用约定定义了函数的低级接口,例如参数如何放置在寄存器或栈中,返回值如何传递,以及谁负责清理栈。

例子

#![allow(unused)]
fn main() {
// Windows API 接口。
unsafe extern "system" { /* ... */ }
}

如果未指定 ABI 字符串,则默认为 "C"

注意

不带显式 ABI 的 extern 语法正在逐步淘汰,因此最好始终显式写入 ABI。

有关更多详细信息,请参阅 Rust 问题 #134986

所有平台都支持以下 ABI 字符串:

  • unsafe extern "Rust" — Rust 函数和闭包的原生调用约定。当声明函数而不使用 extern fn 时,这是默认值。Rust ABI 不提供稳定性保证。
  • unsafe extern "C" — “C” ABI 与目标平台的主流 C 编译器选择的默认 ABI 匹配。
  • unsafe extern "system" — 这等同于 extern "C",但在 Windows x86_32 上除外,对于非变长参数函数,它等同于 "stdcall",而对于变长参数函数,它等同于 "C"

    注意

    由于 Windows 上正确的底层 ABI 与目标平台相关,因此在尝试链接不使用显式定义 ABI 的 Windows API 函数时,最好使用 extern "system"

  • extern "C-unwind"extern "system-unwind" — 分别与 "C""system" 相同,但在被调用者展开(通过 恐慌 或抛出 C++ 风格异常)时具有 不同的行为

还有一些平台特定的 ABI 字符串:

  • unsafe extern "aapcs" — ARM 的软浮点 ABI。

    • 仅适用于 ARM32 目标。
    • “aapcs” 与软浮点 ARM32 上的 “C” ABI 相同。
    • 对应于 clang 的 __attribute__((pcs("aapcs")))

    注意

    有关详细信息,请参阅:

  • unsafe extern "efiapi" — 用于 UEFI 函数的 ABI。
    • 仅适用于 x86 和 ARM 目标(32 位和 64 位)。

"C""system" 类似,大多数平台特定的 ABI 字符串也具有 对应的 -unwind 变体;具体而言,它们是:

  • "aapcs-unwind"
  • "cdecl-unwind"
  • "fastcall-unwind"
  • "stdcall-unwind"
  • "sysv64-unwind"
  • "thiscall-unwind"
  • "win64-unwind"

变长参数函数

外部块中的函数可以通过指定 ... 作为最后一个参数来成为变长的。变长参数可以可选地指定一个标识符。

#![allow(unused)]
fn main() {
unsafe extern "C" {
    unsafe fn foo(...);
    unsafe fn bar(x: i32, ...);
    unsafe fn with_name(format: *const u8, args: ...);
    // 安全性:此函数保证它根本不会访问变长参数。
    safe fn ignores_variadic_arguments(x: i32, ...);
}
}

警告

不应在 extern 块中的函数上使用 safe 限定符,除非该函数保证它根本不会访问变长参数。向变长参数函数传递非预期数量的参数或非预期类型的参数可能会导致 未定义行为

变长参数只能在具有以下 ABI 字符串或其对应 -unwind 变体extern 块中指定:

  • "aapcs"
  • "C"
  • "cdecl"
  • "efiapi"
  • "system"
  • "sysv64"
  • "win64"

外部块上的属性

以下 属性 控制外部块的行为。

link属性

link 属性 指定了编译器应针对 extern 块内的 项 进行链接的原生库的名称。

它使用 MetaListNameValueStr 语法来指定其输入。name 键是要链接的原生库的名称。kind 键是一个可选值,它指定了库的类型,具有以下可能的值:

  • dylib — 表示动态库。如果未指定 kind,则这是默认值。
  • static — 表示静态库。
  • framework — 表示 macOS 框架。这仅对 macOS 目标有效。
  • raw-dylib — 表示一个动态库,编译器将生成一个导入库来与其链接(详见下文的 dylibraw-dylib 比较)。这仅对 Windows 目标有效。

如果指定了 kind,则必须包含 name 键。

可选的 modifiers 参数是一种为要链接的库指定链接修饰符的方法。

修饰符指定为以逗号分隔的字符串,每个修饰符前缀为 +-,分别表示启用或禁用该修饰符。

目前不支持在单个 link 属性中指定多个 modifiers 参数,或者在同一个 modifiers 参数中指定多个相同的修饰符。
示例:#[link(name = "mylib", kind = "static", modifiers = "+whole-archive")]

当从宿主环境导入符号时,wasm_import_module 键可用于为 extern 块中的 项 指定 WebAssembly 模块 名称。如果未指定 wasm_import_module,则默认模块名称为 env

#[link(name = "crypto")]
unsafe extern {
    // …
}

#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern {
    // …
}

#[link(wasm_import_module = "foo")]
unsafe extern {
    // …
}

在空的外部块上添加 link 属性是有效的。您可以使用它来满足代码中其他地方(包括上游 crate)外部块的链接要求,而不是在每个外部块中都添加该属性。

链接修饰符:bundle

此修饰符仅与 static 链接类型兼容。使用任何其他类型都将导致编译器错误。

在构建 rlib 或 staticlib 时,+bundle 意味着原生静态库将被打包到 rlib 或 staticlib 归档中,然后在链接最终二进制文件期间从中检索。

在构建 rlib 时,-bundle 意味着原生静态库被 “按名称” 注册为该 rlib 的依赖项,并且仅在链接最终二进制文件期间才包含其中的目标文件,在最终链接期间也会执行按该名称的文件搜索。
在构建 staticlib 时,-bundle 意味着原生静态库根本不包含在归档中,某些更高级别的构建系统将需要在稍后链接最终二进制文件期间添加它。

在构建可执行文件或动态库等其他目标时,此修饰符没有效果。

此修饰符的默认值为 +bundle

有关此修饰符的更多实现细节,可以在 rustc 的 bundle 文档 中找到。

链接修饰符:whole-archive

此修饰符仅与 static 链接类型兼容。使用任何其他类型都将导致编译器错误。

+whole-archive 意味着静态库作为整个归档进行链接,而不丢弃任何目标文件。

此修饰符的默认值为 -whole-archive

有关此修饰符的更多实现细节,可以在 rustc 的 whole-archive 文档 中找到。

链接修饰符:verbatim

此修饰符与所有链接类型兼容。

+verbatim 意味着 rustc 本身不会向库名称添加任何目标平台指定的库前缀或后缀(如 lib.a),并将尽力向链接器请求同样的内容。

-verbatim 意味着 rustc 在将库名称传递给链接器之前,会向其添加目标平台特定的前缀和后缀,或者不会阻止链接器隐式添加它。

此修饰符的默认值为 -verbatim

有关此修饰符的更多实现细节,可以在 rustc 的 verbatim 文档 中找到。

dylibraw-dylib比较

在 Windows 上,与动态库链接要求向链接器提供一个导入库:这是一个特殊的静态库,它声明了动态库导出的所有符号,其方式使链接器知道它们必须在运行时动态加载。

指定 kind = "dylib" 会指示 Rust 编译器链接一个基于 name 键的导入库。链接器随后将使用其正常的库解析逻辑来查找该导入库。或者,指定 kind = "raw-dylib" 会指示编译器在编译期间生成一个导入库,并将该导入库提供给链接器。

raw-dylib 仅在 Windows 上受支持。针对其他平台使用它将导致编译器错误。

import_name_type

在 x86 Windows 上,函数名称会被 “修饰” (decorated)(即添加特定的前缀和/或后缀)以指示其调用约定。例如,一个名为 fn1 且没有参数的 stdcall 调用约定函数将被修饰为 _fn1@0。然而,PE 格式 也确实允许名称没有前缀或不被修饰。此外,MSVC 和 GNU 工具链对相同的调用约定使用不同的修饰,这意味着默认情况下,某些 Win32 函数无法通过 GNU 工具链使用 raw-dylib 链接类型进行调用。

为了允许这些差异,在使用 raw-dylib 链接类型时,您还可以使用以下值之一指定 import_name_type 键,以更改生成的导入库中函数的命名方式:

  • decorated:函数名称将使用 MSVC 工具链格式进行完整修饰。
  • noprefix:函数名称将使用 MSVC 工具链格式进行修饰,但跳过前导的 ?@ 或可选的 _
  • undecorated:函数名称将不被修饰。

如果未指定 import_name_type 键,则函数名称将使用目标工具链的格式进行完整修饰。

变量永远不会被修饰,因此 import_name_type 键对它们在生成的导入库中的命名方式没有影响。

import_name_type 键仅在 x86 Windows 上受支持。针对其他平台使用它将导致编译器错误。

link_name 属性 可以应用于外部块内的声明,以指定要为给定函数或静态项导入的符号。

例子

#![allow(unused)]
fn main() {
unsafe extern "C" {
    #[link_name = "actual_symbol_name"]
    safe fn name_in_rust();
}
}

link_name 属性使用 MetaNameValueStr 语法。

link_name 属性只能应用于外部块中的函数或 静态项。

注意

rustc 忽略在其他位置的使用,但会针对其发出 lint。这在将来可能会变成错误。

只有 项 上最后一次使用的 link_name 才会生效。

注意

rustc 会针对最后一次使用之前的任何使用发出 lint。这在将来可能会变成错误。

link_name 属性不得与 link_ordinal 属性一起使用。

link_ordinal 属性 可以应用于外部块内的声明,以指示生成要链接的导入库时要使用的数字序数。序数是 Windows 上动态库导出的每个符号的唯一编号,在加载库以查找该符号时可以使用序数,而不必按名称查找。

警告

link_ordinal 仅应在已知符号序数稳定的情况下使用:如果符号的序数在构建其包含的二进制文件时未显式设置,则系统会自动为其分配一个序数,并且分配的序数可能会在二进制文件的不同构建之间发生变化。

#![allow(unused)]
fn main() {
#[cfg(all(windows, target_arch = "x86"))]
#[link(name = "exporter", kind = "raw-dylib")]
unsafe extern "stdcall" {
    #[link_ordinal(15)]
    safe fn imported_function_stdcall(i: i32);
}
}

此属性仅与 raw-dylib 链接类型一起使用。使用任何其他类型都将导致编译器错误。

将此属性与 link_name 属性一起使用将导致编译器错误。

函数参数上的属性

外部函数参数上的属性遵循与 常规函数参数 相同的规则和限制。


  1. 从 2024 版次 开始,unsafe 关键字在语义上是必需的。