Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

链接

Note

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

编译器支持各种方法来静态和动态链接 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 执行编译器,即使该 crate 是为不同目标构建的另一个 crate 的依赖项,目标也将是 x86_64-unknown-linux-gnu

请注意,这些输出是可堆叠的,如果指定了多个,则编译器将生成每种形式的输出而无需重新编译。但是,这仅适用于由同一方法指定的输出。如果只指定了 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 <directory>-l<library> rustc 参数传递任何非 Rust 库,和/或在 Rust 代码中使用 #[link] 指令。如果需要链接 .o 文件,可以使用 -Clink-arg=file.o
  • 使用您的外部链接器。在这种情况下,您首先需要生成 Rust staticlib 目标并将其传递到外部链接器调用中。如果需要链接多个 Rust 子系统,您将需要生成_单个_ staticlib,可能使用大量 extern crate 语句来包含多个 Rust rlib。多个 Rust staticlib 文件可能会冲突。

目前不支持将 rlib 直接传递到外部链接器。

Note

在本节中,使用不同 Rust 运行时实例编译或链接的 Rust 代码被视为“外部代码“。

禁止的链接和展开

只有在二进制文件根据以下规则一致构建时,才能使用 Panic 展开。

如果满足以下任一条件,则 Rust 工件称为可能展开

  • 工件使用 unwind panic 处理器
  • 工件包含使用 unwind panic 策略构建的 crate,该 crate 调用使用 -unwind ABI 的函数。
  • 工件对在另一个具有单独 Rust 运行时副本的 Rust 工件中运行的代码进行 "Rust" ABI 调用,并且该另一个工件可能展开。

Note

此定义捕获了 Rust 工件内的 "Rust" ABI 调用是否曾经展开。

如果 Rust 工件可能展开,则其所有 crate 必须使用 unwind panic 策略构建。否则,展开可能导致未定义行为。

Note

如果您使用 rustc 进行链接,这些规则会自动强制执行。如果您使用 rustc 进行链接,您必须注意确保展开在整个二进制文件中得到一致处理。不使用 rustc 的链接包括使用 dlopen 或类似设施,其中链接由系统运行时完成而不涉及 rustc。这只能在使用不同 -C panic 标志的代码混合时发生,因此大多数用户不必担心这一点。

Note

为保证库在链接时使用的 panic 运行时无论如何都是健全的(并且可与 rustc 链接),可以使用 ffi_unwind_calls lint。该 lint 标记任何对 -unwind 外部函数或函数指针的调用。