Rust’s default linker, ld (or lld on macOS), is a solid workhorse, but it can become a significant bottleneck, especially for large projects, by being slow and producing larger-than-necessary binaries. This is often due to its general-purpose nature and how it handles symbol resolution and relocation information.

The most surprising truth about optimizing Rust builds with linkers is that you can often achieve dramatic improvements in both build speed and binary size by switching to a linker specifically designed for Rust, like mold.

Let’s see mold in action. Imagine a moderately sized Rust project. Compiling it with the default lld might take 15 seconds.

# Example timing with default linker
time cargo build --release

Now, let’s configure Rust to use mold. First, ensure you have mold installed. On most Linux distributions, it’s available via package managers:

# For Debian/Ubuntu
sudo apt install mold

# For Fedora
sudo dnf install mold

# For Arch Linux
sudo pacman -S mold

Once installed, you can tell Cargo to use mold by setting the RUSTFLAGS environment variable or by creating a .cargo/config.toml file. The .cargo/config.toml approach is generally preferred for project-specific or global configurations.

Create or edit .cargo/config.toml in your project’s root directory (or in ~/.cargo/config.toml for a global setting) and add the following:

[target.x86_64-unknown-linux-gnu]
linker = "mold"

[target.x86_64-apple-darwin]
linker = "mold"

[target.x86_64-pc-windows-msvc]
linker = "mold"

Replace x86_64-unknown-linux-gnu with your specific target triple if you’re cross-compiling. For macOS, you might need to ensure mold is installed via Homebrew and potentially use rustup override set target-triple x86_64-apple-darwin and then rustup component add rustc-codegen-lld-toolchain to ensure lld is available if mold doesn’t fully integrate out of the box. However, on modern systems, mold often replaces lld seamlessly.

Now, when you run cargo build --release again:

time cargo build --release

You’ll likely observe a significant reduction in build time, potentially down to 2-5 seconds for the same project.

mold achieves this speedup through several key optimizations:

  • Parallelism: mold is designed from the ground up to leverage multiple CPU cores for linking tasks, processing different parts of the object files and libraries concurrently.
  • Memory Mapping: It uses memory-mapped files extensively, allowing it to read and write data more efficiently without unnecessary copying.
  • Optimized Data Structures: mold employs efficient data structures for symbol tables and relocation entries, speeding up lookups and processing.
  • Incremental Linking: While not strictly a new concept, mold’s implementation is highly optimized for incremental linking, making subsequent builds much faster if only a few files have changed.

Beyond speed, mold also often produces smaller binaries. This is because it’s more aggressive in discarding unused sections and symbols, and it can perform more sophisticated code layout and optimization during the linking phase.

The problem mold solves is the sequential, single-threaded nature of traditional linkers like ld and even lld to a lesser extent. These linkers often perform a series of operations that are inherently difficult to parallelize, such as symbol resolution across all object files and then a pass for relocation patching.

The mental model for linking is essentially a database operation. You have a collection of compiled code and data (object files), and the linker’s job is to consolidate them into a single executable. It needs to:

  1. Collect all symbols: Identify all functions and variables defined and used across all object files.
  2. Resolve symbols: Match every symbol reference to its definition. If a symbol is undefined or multiply defined, it’s an error.
  3. Relocate code and data: Adjust addresses in the code and data sections so that they correctly point to their final locations in the executable.
  4. Produce the executable: Write out the final binary in the correct format (e.g., ELF on Linux, Mach-O on macOS).

Traditional linkers often do this in distinct, sequential phases. mold blurs these lines, performing many of these operations in parallel and with optimized data structures.

A crucial aspect of mold’s performance, often overlooked, is its caching mechanism. It can cache intermediate linking states, which dramatically speeds up incremental builds even further. If you change a single line of code in a large project, mold doesn’t re-link everything from scratch; it intelligently reuses previously computed information, leading to near-instantaneous rebuilds for small changes.

While mold is fantastic for speed and size, it’s not a silver bullet for every linker issue. For extremely niche targets or very old toolchains, you might still encounter compatibility problems. However, for the vast majority of modern Rust development on common architectures, it’s a transformative improvement.

The next hurdle you’ll likely encounter is optimizing the compiler itself, often by exploring different rustc optimization flags or build profiles.

Want structured learning?

Take the full Cargo course →