../dynamic-linking-in-rustc

Dynamic Linking in Rust: Why It's Not What You Think

Rust's prefer-dynamic flag exists, but before you get excited about smaller binaries and shared libraries, you need to understand why this feature is largely academic and why the Rust ecosystem has deliberately moved away from it.

The Reality Check

Rust Doesn't Have a Stable ABI: This is the fundamental issue that makes dynamic linking problematic in Rust. Unlike C/C++, Rust's Application Binary Interface (ABI) is not stable across compiler versions or even different builds of the same version. This means a dynamic library compiled with one version of rustc may be completely incompatible with a binary compiled with another version—even if they're both "stable" releases.

It's Not Really for End Users: The prefer-dynamic flag exists primarily for Rust's own development. Some parts of the Rust toolchain do use dynamic linking (like rustc_driver), but this is more about architectural necessities than a deliberate strategy for faster builds.

How It Works (In Theory)

When you use prefer-dynamic, you're asking rustc to create binaries that depend on shared versions of Rust libraries at runtime. The primary candidate is libstd, but here's the catch: you need these shared libraries to exist on the target system, and they need to be ABI-compatible with your binary.

rustc -C prefer-dynamic main.rs

Or via Cargo config:

[build]
rustflags = ["-C", "prefer-dynamic"]

Why This Rarely Works in Practice

ABI Instability: Even if you manage to create a dynamically linked binary, it will likely break when:

Limited Ecosystem Support: Most crates don't provide dynamic versions because:

Deployment Nightmare: Unlike languages with stable ABIs, distributing Rust dynamic libraries means:

The One Legitimate Use Case

The main scenario where prefer-dynamic makes sense is during development of the Rust toolchain itself. When working on rustc or related tools, dynamic linking can speed up incremental builds because you don't need to relink the entire standard library every time.

For plugin architectures or similar needs, the Rust ecosystem has developed better alternatives:

A More Honest Example

Let's say you have a simple program:

fn main() {
    println!("Hello, world!");
}

Static build (default):

cargo build --release
# Result: ~300KB (includes minimal std portions actually used)

Dynamic build:

# Add to ~/.cargo/config.toml:
# [build]
# rustflags = ["-C", "prefer-dynamic"]

cargo build --release
# Result: ~15KB binary + ~30MB of shared libraries you need to distribute

The "smaller" binary is misleading—you're just moving the bloat elsewhere, and now you have deployment complexity for essentially no benefit.

The Bottom Line

Dynamic linking in Rust isn't a feature you should plan to use in production. It exists for specific internal use cases within the Rust ecosystem, but Rust's design philosophy deliberately favors static linking for good reasons: reliability, performance, and ease of deployment.

If you're coming from languages with stable ABIs and expecting similar dynamic linking benefits, you'll be disappointed. Rust chose a different path that prioritizes memory safety and zero-cost abstractions over traditional dynamic linking models.

The Rust way is to embrace static linking and use the language's other strengths—like excellent dead code elimination and link-time optimization—to achieve the goals you might have been trying to solve with dynamic linking.