链接
注意:本节更多地从编译器的角度而不是语言的角度进行描述。
编译器支持多种方法来静态和动态地链接 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) 来限制。此外,可以删除未使用的 section 以删除所有实际上未使用的依赖项代码(例如,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 始终使用与编译器本身构建的目标相同的目标进行编译。例如,如果您从具有x86_64
CPU 的 Linux 执行编译器,即使 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
传递到您的外部链接器中。