外部块
Syntax
ExternBlock →
unsafe?1 extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
外部块提供未在当前 crate 中定义的项的_声明_,是 Rust 外部函数接口的基础。这些类似于未检查的导入。
调用在外部块中声明的不安全函数或访问不安全静态仅在 unsafe 上下文中允许。
外部块在其所在的模块或块的值命名空间中定义其函数和静态。
unsafe 关键字在语义上必须出现在外部块的 extern 关键字之前。
2024 Edition differences
在 2024 版本之前,
unsafe关键字是可选的。safe和unsafe项限定符仅在外部块本身标记为unsafe时才允许。
函数
外部块中的函数以与其他 Rust 函数相同的方式声明,不同之处在于它们不能有主体,而是以分号终止。
参数中不允许使用模式,只能使用 IDENTIFIER 或 _。
允许使用 safe 和 unsafe 函数限定符,但不允许使用其他函数限定符(例如 const、async、extern)。
外部块中的函数可以像在 Rust 中定义的函数一样被 Rust 代码调用。Rust 编译器自动在 Rust ABI 和外部 ABI 之间进行转换。
在 extern 块中声明的函数除非存在 safe 函数限定符,否则是隐式 unsafe 的。
当强制转换为函数指针时,在 extern 块中声明的函数类型为 for<'l1, ..., 'lm> extern "abi" fn(A1, ..., An) -> R,其中 'l1、… 'lm 是其生命周期参数,A1、…、An 是其参数的声明类型,R 是声明的返回类型。
静态
外部块中的静态以与外部块外部的静态相同的方式声明,不同之处在于它们没有初始化其值的表达式。
除非 extern 块中声明的静态项被限定为 safe,否则访问该项是 unsafe 的,无论它是否可变,因为没有任何东西保证静态内存中的位模式对其声明的类型有效,因为某些任意(例如 C)代码负责初始化静态。
外部静态可以像外部块外部的静态一样是不可变或可变的。
不可变静态必须在执行任何 Rust 代码之前初始化。在 Rust 代码读取之前初始化静态是不够的。一旦 Rust 代码运行,修改不可变静态(从 Rust 内部或外部)是 UB,除非修改发生在 UnsafeCell 内部的字节上。
ABI
extern 关键字后面可以跟一个可选的 ABI 字符串。ABI 指定块中函数的调用约定。调用约定定义了函数的低级接口,例如参数如何放置在寄存器或栈上,如何传递返回值,以及谁负责清理栈。
Example
#![allow(unused)] fn main() { // Interface to the Windows API. unsafe extern "system" { /* ... */ } }
如果未指定 ABI 字符串,则默认为 "C"。
Note
没有显式 ABI 的
extern语法正在逐步淘汰,因此最好始终显式编写 ABI。有关更多详细信息,请参阅 Rust issue #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"。Note
由于 Windows 上正确的底层 ABI 是特定于目标的,因此在尝试链接不使用显式定义的 ABI 的 Windows API 函数时,最好使用
extern "system"。
extern "C-unwind"和extern "system-unwind"— 分别与"C"和"system"相同,但当被调用者展开(通过 panic 或抛出 C++ 风格的异常)时具有不同的行为。
还有一些特定于平台的 ABI 字符串:
-
unsafe extern "cdecl"— 通常与 x86_32 C 代码一起使用的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__cdecl和 GCC 及 clang 的__attribute__((cdecl))。
-
unsafe extern "stdcall"— 通常由 x86_32 上的 Win32 API 使用的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__stdcall和 GCC 及 clang 的__attribute__((stdcall))。
-
unsafe extern "win64"— Windows x64 ABI。- 仅在 x86_64 目标上可用。
- “win64” 与 Windows x86_64 目标上的 “C” ABI 相同。
- 对应于 GCC 及 clang 的
__attribute__((ms_abi))。
-
unsafe extern "sysv64"— System V ABI。- 仅在 x86_64 目标上可用。
- “sysv64” 与非 Windows x86_64 目标上的 “C” ABI 相同。
- 对应于 GCC 及 clang 的
__attribute__((sysv_abi))。
-
unsafe extern "aapcs"— ARM 的软浮点 ABI。- 仅在 ARM32 目标上可用。
- “aapcs” 与软浮点 ARM32 上的 “C” ABI 相同。
- 对应于 clang 的
__attribute__((pcs("aapcs")))。
Note
有关详细信息,请参阅:
-
unsafe extern "fastcall"— stdcall 的“快速“变体,在寄存器中传递一些参数。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__fastcall和 GCC 及 clang 的__attribute__((fastcall))。
-
unsafe extern "thiscall"— 通常在 x86_32 MSVC 上用于 C++ 类成员函数的调用约定。- 仅在 x86_32 目标上可用。
- 对应于 MSVC 的
__thiscall和 GCC 及 clang 的__attribute__((thiscall))。
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: ...);
// SAFETY: This function guarantees it will not access
// variadic arguments.
safe fn ignores_variadic_arguments(x: i32, ...);
}
}
Warning
除非该函数保证完全不会访问可变参数,否则不应在
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— 表示编译器将生成导入库以链接的动态库(有关详细信息,请参阅下面的dylib与raw-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 文档。
链接修饰符:whole-archive
此修饰符仅与 static 链接类型兼容。使用任何其他类型将导致编译器错误。
+whole-archive 意味着静态库作为整个归档链接,不丢弃任何目标文件。
此修饰符的默认值为 -whole-archive。
有关此修饰符的更多实现细节,请参阅 whole-archive 文档。
链接修饰符:verbatim
此修饰符与所有链接类型兼容。
+verbatim 意味着 rustc 本身不会向库名称添加任何目标指定的库前缀或后缀(如 lib 或 .a),并将尽力要求链接器执行相同操作。
-verbatim 意味着 rustc 要么在将库名称传递给链接器之前添加特定于目标的前缀和后缀,要么不会阻止链接器隐式添加它。
此修饰符的默认值为 -verbatim。
有关此修饰符的更多实现细节,请参阅 verbatim 文档。
dylib 与 raw-dylib
在 Windows 上,链接动态库需要向链接器提供导入库:这是一种特殊的静态库,它声明动态库导出的所有符号,以便链接器知道它们必须在运行时动态加载。
指定 kind = "dylib" 指示 Rust 编译器基于 name 键链接导入库。然后链接器将使用其正常的库解析逻辑来查找该导入库。或者,指定 kind = "raw-dylib" 指示编译器在编译期间生成导入库并将其提供给链接器。
raw-dylib 仅在 Windows 上受支持。在针对其他平台使用它将导致编译器错误。
import_name_type 键
在 x86 Windows 上,函数名称被“修饰“(即添加特定的前缀和/或后缀)以指示其调用约定。例如,名为 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 属性
link_name 属性 可应用于 extern 块内的声明,以指定为给定函数或静态导入的符号。
Example
#![allow(unused)] fn main() { unsafe extern "C" { #[link_name = "actual_symbol_name"] safe fn name_in_rust(); } }
link_name 属性使用 MetaNameValueStr 语法。
符号名称不能为空字符串或包含任何 U+0000 (NUL) 字节。
link_name 属性只能应用于 extern 块中的函数或静态项。
Note
rustc会忽略在其他位置的使用,但会发出 lint 警告。这可能在未来成为错误。
只有项上的第一次使用 link_name 才有效果。
Note
rustc会对第一次之后的任何使用发出 lint 警告,并带有未来兼容性警告。这可能在未来成为错误。
link_name 属性不能与 link_ordinal 属性一起使用。
link_ordinal 属性
link_ordinal 属性可应用于 extern 块内的声明,以指示生成导入库以链接时使用的数字序号。序号是 Windows 上动态库导出的每个符号的唯一编号,可用于在加载库时查找该符号,而不必按名称查找。
Warning
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 函数参数上的属性遵循与常规函数参数相同的规则和限制。
-
从 2024 版本开始,
unsafe关键字在语义上是必需的。 ↩