链接

注意:本节更多地从编译器的角度而非语言的角度进行描述。

编译器支持多种静态和动态链接 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) 来限制。此外,可以删除未使用的部分以删除所有未实际使用的依赖项代码(例如,--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 的编译器,则目标将为 x86_64-unknown-linux-gnu,即使该 crate 是为不同目标构建的另一个 crate 的依赖项。

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

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

  1. 如果要生成静态库,则所有上游依赖项都必须以 rlib 格式提供。此要求源于动态库无法转换为静态格式的原因。

    请注意,不可能将原生动态依赖项链接到静态库中,在这种情况下,将打印有关所有未链接的原生动态依赖项的警告。

  1. 如果要生成 rlib 文件,则对上游依赖项的可用格式没有任何限制。只需要所有上游依赖项都可用于从中读取元数据即可。

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

  1. 如果要生成可执行文件并且未指定 -C prefer-dynamic 标志,则首先尝试以 rlib 格式查找依赖项。如果某些依赖项不是以 rlib 格式提供的,则会尝试动态链接(请参见下文)。
  1. 如果要生成动态库或正在动态链接的可执行文件,则编译器将尝试协调 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

混合 Rust 和外部代码库

如果您将 Rust 与外部代码(例如 C、C++)混合在一起,并希望制作一个包含两种类型代码的单个二进制文件,则可以使用两种方法进行最终的二进制文件链接

  • 使用 rustc。通过 -L <目录>-l<库> rustc 参数,和/或在你的 Rust 代码中使用 #[link] 指令来传递任何非 Rust 库。如果你需要链接 .o 文件,可以使用 -Clink-arg=file.o
  • 使用你的外部链接器。在这种情况下,你首先需要生成一个 Rust staticlib 目标,并将其传递给你的外部链接器调用。如果你需要链接多个 Rust 子系统,你可能需要生成一个单独的 staticlib,可能需要使用大量的 extern crate 语句来包含多个 Rust rlib 文件。多个 Rust staticlib 文件可能会冲突。

目前不支持直接将 rlib 文件传递给你的外部链接器。