链接
注意
本节更多地从编译器的角度而非语言的角度进行描述。
编译器支持各种方法来静态和动态地链接 crate。本节将探讨链接 crate 的各种方法,有关原生库的更多信息可以在书中的 FFI 部分找到。
在一次编译会话中,编译器可以通过命令行标志或 crate_type 属性生成多个构建产物。如果指定了一个或多个命令行标志,所有 crate_type 属性将被忽略,仅构建命令行指定的产物。
--crate-type=bin,#![crate_type = "bin"]- 将生成一个可运行的可执行文件。这要求 crate 中存在一个main函数,该函数将在程序开始执行时运行。这将链接所有 Rust 和原生依赖项,生成一个可分发的单一二进制文件。这是默认的 crate 类型。
--crate-type=lib,#![crate_type = "lib"]- 将生成一个 Rust 库。这是一个模糊的概念,因为库可以以多种形式呈现。此通用lib选项的目的是生成“编译器推荐”样式的库。输出库始终可供 rustc 使用,但库的实际类型可能会不时更改。其余的输出类型都是不同形式的库,而lib类型可以视为其中一种的别名(但实际是哪一种由编译器定义)。
--crate-type=dylib,#![crate_type = "dylib"]- 将生成一个动态 Rust 库。这与lib输出类型不同,因为它强制生成动态库。生成的动态库可用作其他库和/或可执行文件的依赖项。此输出类型将在 Linux 上创建*.so文件,在 macOS 上创建*.dylib文件,在 Windows 上创建*.dll文件。
-
--crate-type=staticlib,#![crate_type = "staticlib"]- 将生成一个静态系统库。这与其他库输出不同,因为编译器永远不会尝试链接到staticlib输出。此输出类型的目的是创建一个包含所有本地 crate 代码及其所有上游依赖项的静态库。此输出类型将在 Linux、macOS 和 Windows (MinGW) 上创建*.a文件,在 Windows (MSVC) 上创建*.lib文件。建议在将 Rust 代码链接到现有非 Rust 应用程序等情况下使用此格式,因为它不会动态依赖于其他 Rust 代码。请注意,静态库可能拥有的任何动态依赖项(例如对系统库的依赖,或对编译为动态库的 Rust 库的依赖)在从某个地方链接该静态库时必须手动指定。
--print=native-static-libs标志可能对此有所帮助。请注意,由于生成的静态库包含所有依赖项(包括标准库)的代码,并且还导出了它们的所有公共符号,因此将静态库链接到可执行文件或共享库可能需要特别小心。在共享库的情况下,导出的符号列表必须通过例如链接器或符号版本脚本、导出的符号列表 (macOS) 或模块定义文件 (Windows) 来限制。此外,可以删除未使用的节以删除实际上未使用的所有依赖项代码(例如,Linux 上的
--gc-sections或 macOS 上的-dead_strip)。
--crate-type=cdylib,#![crate_type = "cdylib"]- 将生成一个动态系统库。这用于编译从另一种语言加载的动态库。此输出类型将在 Linux 上创建*.so文件,在 macOS 上创建*.dylib文件,在 Windows 上创建*.dll文件。
--crate-type=rlib,#![crate_type = "rlib"]- 将生成一个“Rust 库”文件。这用作中间构建产物,可以认为是“静态 Rust 库”。这些rlib文件与staticlib文件不同,它们在未来的链接中由编译器解释。这本质上意味着rustc会在rlib文件中查找元数据,就像它在动态库中查找元数据一样。此形式的输出用于生成静态链接的可执行文件以及staticlib输出。
--crate-type=proc-macro,#![crate_type = "proc-macro"]- 生成的输出未指定,但如果提供了-L路径,编译器将把输出构建产物识别为宏,并可加载到程序中。使用此 crate 类型编译的 crate 只能导出过程宏。编译器将自动设置proc_macro配置选项。这些 crate 总是使用编译器自身构建时的相同目标进行编译。例如,如果您正在从 Linux 运行使用x86_64CPU 构建的编译器,即使该 crate 是为不同目标构建的其他 crate 的依赖项,目标也将是x86_64-unknown-linux-gnu。
请注意,这些输出是可以叠加的,即如果指定了多个,则编译器将生成每种形式的输出而无需重新编译。但是,这仅适用于通过相同方法指定的输出。如果仅指定了 crate_type 属性,则所有这些都将构建,但如果指定了一个或多个 --crate-type 命令行标志,则仅构建这些输出。
有了所有这些不同类型的输出,如果 crate A 依赖于 crate B,那么编译器可以在整个系统中找到多种不同形式的 B。然而,编译器查找的唯一形式是 rlib 格式和动态库格式。对于依赖库的这两种选项,编译器必须在某个时候在这两种格式之间做出选择。考虑到这一点,编译器在确定将使用何种依赖项格式时遵循以下规则
-
如果正在生成静态库,则要求所有上游依赖项以
rlib格式提供。此要求源于动态库无法转换为静态格式的原因。请注意,将原生动态依赖项链接到静态库是不可能的,在这种情况下,将打印所有未链接的原生动态依赖项的警告。
-
如果正在生成
rlib文件,则对上游依赖项的可用格式没有限制。只需所有上游依赖项可用于从中读取元数据即可。这是因为
rlib文件不包含任何上游依赖项。如果所有rlib文件都包含一份libstd.rlib的副本,那效率会很低!
- 如果正在生成可执行文件且未指定
-C prefer-dynamic标志,则首先尝试以rlib格式查找依赖项。如果某些依赖项没有 rlib 格式可用,则尝试动态链接(见下文)。
-
如果正在生成动态库或正在动态链接的可执行文件,则编译器将尝试协调 rlib 或 dylib 格式的可用依赖项以创建最终产品。
编译器的一个主要目标是确保任何构建产物中同一库只出现一次。例如,如果动态库 B 和 C 各自静态链接到库 A,那么一个 crate 就不能同时链接 B 和 C,因为 A 会有两个副本。编译器允许混合使用 rlib 和 dylib 格式,但必须满足此限制。
编译器目前没有实现任何提示库应以何种格式链接的方法。在动态链接时,编译器将尝试最大化动态依赖项,同时仍允许某些依赖项通过 rlib 链接。
在大多数情况下,如果进行动态链接,建议所有库都以 dylib 形式提供。对于其他情况,如果编译器无法确定每个库应以何种格式链接,它将发出警告。
总的来说,对于所有编译需求,--crate-type=bin 或 --crate-type=lib 应该足够了,其他选项只是在需要对 crate 的输出格式进行更精细控制时才可用。
静态和动态 C 运行时
标准库通常会努力为适当的目标提供静态链接和动态链接的 C 运行时支持。例如,x86_64-pc-windows-msvc 和 x86_64-unknown-linux-musl 目标通常带有这两种运行时,用户可以选择他们喜欢的。编译器中的所有目标都有一个默认的链接 C 运行时模式。通常目标默认是动态链接,但也有一些默认静态链接的例外,例如
arm-unknown-linux-musleabiarm-unknown-linux-musleabihfarmv7-unknown-linux-musleabihfi686-unknown-linux-muslx86_64-unknown-linux-musl
C 运行时的链接配置为尊重 crt-static 目标特性。这些目标特性通常通过传递给编译器的标志在命令行中配置。例如,要启用静态运行时,您将执行
rustc -C target-feature=+crt-static foo.rs
而要动态链接到 C 运行时,您将执行
rustc -C target-feature=-crt-static foo.rs
不支持切换 C 运行时链接的目标将忽略此标志。建议在编译器成功后检查生成的二进制文件,以确保其按您预期的方式链接。
Crate 也可以了解 C 运行时是如何链接的。例如,MSVC 上的代码需要根据链接的运行时进行不同的编译(例如,使用 /MT 或 /MD)。这目前通过 cfg 属性的 target_feature 选项导出。
#![allow(unused)] fn main() { #[cfg(target_feature = "crt-static")] fn foo() { println!("the C runtime should be statically linked"); } #[cfg(not(target_feature = "crt-static"))] fn foo() { println!("the C runtime should be dynamically linked"); } }
另请注意,Cargo 构建脚本可以通过环境变量了解此特性。在构建脚本中,您可以通过以下方式检测链接:
use std::env; fn main() { let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new()); if linkage.contains("crt-static") { println!("the C runtime will be statically linked"); } else { println!("the C runtime will be dynamically linked"); } }
要在本地使用此特性,您通常会使用 RUSTFLAGS 环境变量通过 Cargo 向编译器指定标志。例如,要在 MSVC 上编译一个静态链接的二进制文件,您将执行
RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows-msvc
混合 Rust 和外部代码库
如果您将 Rust 与外部代码(例如 C、C++)混合使用,并希望创建一个包含这两种类型代码的单一二进制文件,则对于最终的二进制链接,您有两种方法:
- 使用
rustc。使用-L <directory>和-l<library>rustc 参数,以及/或 Rust 代码中的#[link]指令来传递任何非 Rust 库。如果您需要链接.o文件,可以使用-Clink-arg=file.o。 - 使用您的外部链接器。在这种情况下,您首先需要生成一个 Rust
staticlib目标,并将其传递给您的外部链接器调用。如果您需要链接多个 Rust 子系统,您将需要生成一个*单一*的staticlib,可能使用大量的extern crate语句来包含多个 Rustrlib。多个 Ruststaticlib文件可能会冲突。
目前不支持将 rlib 直接传递给您的外部链接器。
注意
为了本节的目的,使用 Rust 运行时不同实例编译或链接的 Rust 代码算作“外部代码”。
禁止的链接和 unwinding
只有二进制文件按照以下规则一致构建时,才能使用 panic unwinding。
如果满足以下任一条件,Rust 构建产物被称为潜在 unwinding 的:
- 构建产物使用
unwindpanic 处理器。 - 构建产物包含一个使用
unwindpanic 策略构建的 crate,并且该 crate 调用了一个使用-unwindABI 的函数。 - 该构建产物对在另一个具有独立 Rust 运行时副本的 Rust 构建产物中运行的代码进行
"Rust"ABI 调用,并且该另一个构建产物是潜在 unwinding 的。
注意
此定义捕获了 Rust 构建产物内的
"Rust"ABI 调用是否可能发生 unwinding。
如果 Rust 构建产物是潜在 unwinding 的,则其所有 crate 都必须使用 unwind panic 策略构建。否则,unwinding 可能导致未定义行为。
注意
如果您使用
rustc进行链接,这些规则会自动强制执行。如果您*不*使用rustc进行链接,则必须注意确保在整个二进制文件中一致地处理 unwinding。不使用rustc进行链接包括使用dlopen或类似设施,其中链接由系统运行时完成,不涉及rustc。这只可能发生在混合使用不同-C panic标志的代码时,因此大多数用户不必担心这一点。
注意
为了保证无论链接时使用何种 panic 运行时,库都是健全的(并且可以与
rustc链接),可以使用ffi_unwind_callslint。此 lint 会标记对-unwind外部函数或函数指针的任何调用。