链接

注意: 这部分所描述的内容,主要是从编译器角度进行,而非语言。

编译器支持静态和动态地将不同的 crate 链接起来。 本节将探讨链接 crate 的各种方法,有关本地库的更多信息可以参阅《Rust 程序设计语言》中 FFI 章节

在一次编译会话中,编译器可以通过使用命令行标志或 crate_type 属性生成多个制品。 如果指定了一个或多个命令行标志,则忽略所有 crate_type 属性,仅构建由命令行所指定的制品。

  • --crate-type=bin#![crate_type = "bin"] - 将生成可运行的可执行文件。 在 crate 中需要有一个 main 函数,程序开始执行时,首先运行该函数。 并将链接所有的语言以及本地的依赖项,生成可分发的单个二进制文件。 这是 crate 的默认类型。

  • --crate-type=lib#![crate_type = "lib"] - 将生成 "Rust 库" 。这是一个泛指的选项,因为库有多种形式。 其表示生成 "编译器推荐" 的库格式。目前生成的库,意味着对于 rustc 始终是可用的,但实际所生成库的类型可能会随时间而变化。 其余的输出类型都表示了单独的且不同的库类型,而 lib 类型可以看作是其中一个的别名 (但实际的类型由编译器定义) 。

  • --crate-type=dylib#![crate_type = "dylib"] - 将生成动态 Rust 库。这与 lib 输出类型不同,此选项强制生成动态库。 所生成的动态库可以用作其他库和/或可执行文件的依赖项。 此输出类型将在 Linux 上创建 *.so 文件,在 macOS 上创建 *.dylib 文件,在 Windows 上创建 *.dll 文件。

  • --crate-type=staticlib#![crate_type = "staticlib"] - 将生成静态系统库。 这与其他库输出不同,因为 Rust 编译器决不会尝试链接 staticlib 格式的库。 此输出类型的可以创建一个包含所有本地 crate 代码以及所有上游依赖项的静态库, 将在 Linux、macOS 和 Windows (MinGW) 上创建 *.a 文件,在 Windows (MSVC) 上创建 *.lib 文件。 此格式推荐在将 Rust 代码链接到现有的非 Rust 应用程序时使用,因为这一格式的库不会对其他 Rust 代码产生动态依赖。

  • --crate-type=cdylib#![crate_type = "cdylib"] - 将生成一个动态系统库。 这用于编译可从另一种语言中加载的动态库。 此输出类型将在 Linux 上创建 *.so 文件,在 macOS 上创建 *.dylib 文件,在 Windows 上创建 *.dll 文件。

  • --crate-type=rlib#![crate_type = "rlib"] - 将生成 "Rust库" 文件。这用作中间制品,可以看作是 "静态Rust库" 。 这些 rlib 文件不像 staticlib 文件一样被编译器静态链接。 编译器在将来的链接中将解释这些 rlib文件,这基本上意味着 rustc 将像在动态库中查找元数据一样查找 rlib 文件中的元数据。 这种输出形式用于生成静态链接的可执行文件以及 staticlib 输出。

  • --crate-type=proc-macro#![crate_type = "proc-macro"] - 产生的输出未指定,但如果提供了 -L 路径,则编译器将识别输出制品为宏,它可以加载到程序中。 使用此 crate 类型编译的 crate 必须仅导出 过程宏 。编译器将自动设置 proc_macro 配置选项 。这些crate总是使用编译器自身构建的相同目标进行编译。 例如,如果您从 Linux 执行编译器,并且使用 x86_64 CPU,则目标将是 x86_64-unknown-linux-gnu ,即使 crate 是正在为不同目标构建的另一个 crate 的依赖项。

请注意,这些输出是可叠加的,这意味着如果指定多个,则编译器将生成每种形式的输出而无需重新编译。 但是,这仅适用于由同一方法指定的输出。如果仅指定了 crate_type 属性,则将构建所有输出,但是如果指定了一个或多个 --crate-type 命令行标志,则仅构建这些输出。

有了这些不同类型的输出,如果 crate A 依赖于 crate B ,那么编译器可以在系统中以各种不同的形式找到 B 。 然而,编译器查找的唯一格式是 rlib 格式和动态库格式。有了这两个选项,编译器必须在这两种格式之间做出选择。 考虑到这一点,编译器在确定将使用哪种依赖关系格式时遵循以下规则:

  1. 如果正在生成静态库,则所有上游依赖项都必须以 rlib 格式可用。这个要求因为动态库无法转换为静态格式。

    请注意,无法将本机动态依赖项链接到静态库中,在这种情况下,将打印所有未链接的本机动态依赖项的警告。

  2. 如果正在生成一个 rlib 文件,则上游依赖项可用的格式没有限制。只需要所有上游依赖项都可用于读取元数据。

    这样做的原因是, rlib 文件不包含任何上游依赖项。所有 rlib 文件包含 libstd.rlib 的副本时效率不高!

  3. 如果正在生成可执行文件且未指定 -C prefer-dynamic 标志,则首先尝试在 rlib 格式中查找依赖项。 如果某些依赖项在 rlib 格式中不可用,则尝试动态链接 (见下文) 。

  4. 如果正在生成动态库或正在以动态链接方式链接的可执行文件,则编译器将尝试在 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-msvcx86_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