外部块

语法
ExternBlock :
   unsafe?1 extern Abi? {
      InnerAttribute*
      ExternalItem*
   }

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

1

从 2024 Edition 开始,语义上要求使用 unsafe 关键字。

外部块提供了当前 crate 中未定义的项的声明,并且是 Rust 外部函数接口的基础。这些类似于未检查的导入。

外部块中允许两种类型的项声明函数静态变量

调用在外部块中声明的函数或访问静态变量仅在 unsafe 上下文中被允许。

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

在外部块上,语义上要求 unsafe 关键字出现在 extern 关键字之前。

Edition 差异:在 2024 edition 之前,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)代码负责初始化静态变量。

Extern 静态变量可以是不可变的或可变的,就像外部块外的 静态变量 一样。

不可变的静态变量必须在任何 Rust 代码执行之前初始化。静态变量在 Rust 代码读取它之前初始化是不够的。一旦 Rust 代码运行,变异不可变的静态变量(从 Rust 内部或外部)是 UB,除非变异发生在 UnsafeCell 内部的字节中。

ABI

默认情况下,外部块假定它们调用的库使用特定平台上的标准 C ABI。可以使用 abi 字符串指定其他 ABI,如下所示

#![allow(unused)]
fn main() {
#[cfg(any(windows, target_arch = "x86"))]
// Interface to the Windows API
unsafe extern "stdcall" { }
}

有三个跨平台的 ABI 字符串,所有编译器都保证支持

  • unsafe extern "Rust" – 当你在任何 Rust 代码中编写普通的 fn foo() 时,这是默认的 ABI。
  • unsafe extern "C" – 这与 extern fn foo() 相同;无论你的 C 编译器支持什么默认值。
  • unsafe extern "system" – 通常与 extern "C" 相同,但在 Win32 上,它是 "stdcall",或者你应该用来链接到 Windows API 本身的内容

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

  • unsafe extern "cdecl" – x86_32 C 代码的默认值。
  • unsafe extern "stdcall" – x86_32 上 Win32 API 的默认值。
  • unsafe extern "win64" – x86_64 Windows 上 C 代码的默认值。
  • unsafe extern "sysv64" – 非 Windows x86_64 上 C 代码的默认值。
  • unsafe extern "aapcs" – ARM 的默认值。
  • unsafe extern "fastcall"fastcall ABI – 对应于 MSVC 的 __fastcall 和 GCC 及 clang 的 __attribute__((fastcall))
  • unsafe extern "vectorcall"vectorcall ABI – 对应于 MSVC 的 __vectorcall 和 clang 的 __attribute__((vectorcall))
  • unsafe extern "thiscall" – MSVC 上 C++ 成员函数的默认值 – 对应于 MSVC 的 __thiscall 和 GCC 及 clang 的 __attribute__((thiscall))
  • unsafe extern "efiapi" – 用于 UEFI 函数的 ABI。

可变参数函数

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

#![allow(unused)]
fn main() {
unsafe extern "C" {
    safe fn foo(...);
    unsafe fn bar(x: i32, ...);
    unsafe fn with_name(format: *const u8, args: ...);
}
}

外部块上的属性

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

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 {
    // …
}

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

链接修饰符:bundle

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

当构建 rlib 或 staticlib 时,+bundle 表示本地静态库将被打包到 rlib 或 staticlib 存档中,然后在最终二进制文件的链接期间从那里检索。

当构建 rlib 时,-bundle 表示本地静态库被“按名称”注册为该 rlib 的依赖项,并且仅在最终二进制文件的链接期间包含来自它的目标文件,按名称的文件搜索也在最终链接期间执行。
当构建 staticlib 时,-bundle 表示本地静态库根本不包含在存档中,并且一些更高级别的构建系统将需要在最终二进制文件的链接期间稍后添加它。

当构建其他目标(如可执行文件或动态库)时,此修饰符无效。

此修饰符的默认值为 +bundle

有关此修饰符的更多实现细节,请参阅 bundle rustc 文档

链接修饰符:whole-archive

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

+whole-archive 表示静态库作为整个存档链接,而不会丢弃任何目标文件。

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

有关此修饰符的更多实现细节,请参阅 whole-archive rustc 文档

链接修饰符:verbatim

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

+verbatim 表示 rustc 本身不会向库名称添加任何目标指定的库前缀或后缀(如 lib.a),并将尽力要求链接器执行相同的操作。

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

此修饰符的默认值为 -verbatim

有关此修饰符的更多实现细节,请参阅 verbatim rustc 文档

dylibraw-dylib

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

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

raw-dylib 仅在 Windows 上受支持。在以其他平台为目标时使用它将导致编译器错误。

import_name_type

在 x86 Windows 上,函数名称是“修饰的”(即,添加了特定的前缀和/或后缀)以指示它们的调用约定。例如,一个名为 fn1stdcall 调用约定函数,没有参数将被修饰为 _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 属性 可以在 extern 块内的声明上指定,以指示要为给定函数或静态变量导入的符号。

它使用 MetaNameValueStr 语法来指定符号的名称。

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

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

link_ordinal 属性 可以应用于 extern 块内的声明,以指示在生成要链接的导入库时要使用的数字序号。序号是 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 属性一起使用将导致编译器错误。

函数参数上的属性

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