链接
注意
本节更多地从编译器的角度而非语言的角度进行描述。
编译器支持各种方法来静态和动态地链接 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_64
CPU 构建的编译器,即使该 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-musleabi
arm-unknown-linux-musleabihf
armv7-unknown-linux-musleabihf
i686-unknown-linux-musl
x86_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 的:
- 构建产物使用
unwind
panic 处理器。 - 构建产物包含一个使用
unwind
panic 策略构建的 crate,并且该 crate 调用了一个使用-unwind
ABI 的函数。 - 该构建产物对在另一个具有独立 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_calls
lint。此 lint 会标记对-unwind
外部函数或函数指针的任何调用。