Skip to content
rusthacks.dev

Rust Error Linking With cc Failed and Common Causes

The linker failed after compiling succeeded. Read the last lines for the missing symbol or library, then install the dev package, fix the target, or switch to a Rust-only dependency.

7 min read
Summarize in AI
Flat illustration for the post “Rust Error Linking With cc Failed and Common Causes”, composed around errors.

This is the Rust error that looks the most alarming and is the least about Rust. error: linking with `cc` failed: exit status: 1 arrives followed by a command line that scrolls for pages, and the instinct is to search for the first words of it, which are the same for every cause. The information you need is at the bottom, in a few lines the linker wrote. This post explains where the error comes from, shows how to read the part that matters and goes through the four causes that account for nearly every report, each with the real message. The macOS cross-build output below was captured on Rust 1.99.

What failed, and what did not

Building a Rust binary has two stages. The compiler, rustc, turns your crates into object files. Then it invokes the system linker, which on most platforms is the C compiler driver cc, to combine those objects with the standard library and any native libraries into an executable. The error means the second stage returned a nonzero status. Your Rust code compiled. That narrows things considerably: type errors and borrow checker complaints never look like this, and a program that gets to this point is syntactically and semantically fine.

The error has a fixed shape. A long "cc" "-m64" ... command appears in a note:, followed by "some arguments are omitted", and then, further down, the output of the linker itself. That output is the only part worth reading:

Text
error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "-m64" "/work/target/x86_64-unknown-linux-gnu/debug/deps/rustc.../symbols.o" "<7 object files omitted>" ...
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
          ld: unknown options: --as-needed -Bstatic -Bdynamic --eh-frame-hdr -z --gc-sections -z -z
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

Skip to the last lines of the last note. Here the verdict is "ld: unknown options". The rest of this post is what to do depending on what that last note says.

Cause one: there is no C toolchain

On a minimal Linux image, container or fresh WSL install, the linker simply is not there, and the message is short and sits near the top. It names the linker Cargo tried to run. The block below comes from pointing a build at a program that does not exist with RUSTFLAGS="-C linker=no-such-cc"; on a bare machine the name in backticks is cc:

Text
error: linker `no-such-cc` not found
  |
  = note: No such file or directory (os error 2)

error: could not compile `lk` (bin "lk") due to 1 previous error

Rust ships its own compiler but not a linker, so every machine that builds Rust needs a C toolchain. Install one:

Shell
sudo apt-get install -y build-essential pkg-config
sudo dnf install -y gcc pkgconf-pkg-config
xcode-select --install

The three lines are for Debian or Ubuntu, Fedora and macOS in that order. In Docker, this is why rust:1.99-slim sometimes fails where rust:1.99 works: the slim image drops gcc and the full image keeps it. The installation walkthrough in installing Rust with rustup lists the prerequisites per system.

Cause two: a native library's development files are missing

When a crate wraps a C library, linking needs that library's files, not just its runtime. The giveaway is a line naming the library. This is the macOS spelling, produced by a #[link(name = "nosuchlib")] block; GNU ld on Linux words the same failure as /usr/bin/ld: cannot find -lssl: No such file or directory, with -lssl being the library:

Text
error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" ".../symbols.o" "<8 object files omitted>" "-lnosuchlib"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: ld: library 'nosuchlib' not found
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

The -l prefix on the command line means "link this library", so -lssl is libssl, and the linker cannot find libssl.so or libssl.a in its search path. Runtime packages install the versioned file (libssl.so.3) but not the unversioned libssl.so link or the headers; those come from the development package, named libssl-dev on Debian and openssl-devel on Fedora. Install it, or avoid the system library altogether by choosing a pure Rust alternative. The OpenSSL case has its own page, could not find OpenSSL, and reqwest with rustls avoids the dependency by design. For a library with no Rust alternative, a vendored feature that compiles the C source as part of the build removes the system dependency at the cost of a C compiler and build time, as discussed in linking native libraries from build scripts.

Cause three: the wrong linker for the target

The macOS output above is this case. A build with --target x86_64-unknown-linux-gnu on an Apple machine compiles fine, then macOS's ld receives flags that only the GNU linker understands (--as-needed, --gc-sections, -z), and rejects them. The same happens from Linux when targeting macOS or Windows, and when you build for aarch64-unknown-linux-gnu on an x86 machine with the host's cc. The compiler does not bring a linker for foreign targets. Tell Cargo which one to use for that target:

.cargo/config.tomlTOML
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-linux-gnu-gcc"

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

On Debian or Ubuntu those compilers come from gcc-x86-64-linux-gnu and gcc-aarch64-linux-gnu; from a Mac, a cross toolchain, cargo-zigbuild (which uses zig cc as the linker) or the cross tool with a container is simpler. Target triples explained shows how the parts of a triple map to what the linker needs, and cross-compiling from macOS to Linux works through the toolchain choices.

Cause four: undefined symbols

When the linker finds no definition for a function the code calls, the message lists the symbols. This one comes from declaring an extern "C" function that nothing defines; on Linux the wording is undefined reference to `nosuch_fn':

Text
error: linking with `cc` failed: exit status: 1
  |
  = note: Undefined symbols for architecture arm64:
            "_nosuch_fn", referenced from:
                __RNvCsbz9MxhfdLcL_2lk4main in lk-25176310d6a256a3.aukxqb3pk54vggw747wfh00nv.1ob56te.rcgu.o
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

Typical reasons are a native library that is present for the wrong architecture (an x86 libz on an arm64 build, common with Homebrew installs under Rosetta), a library that is too old and lacks the function the crate calls, or link order on some linkers, where a library must appear after the objects that need it. Check the architecture with file /path/to/libz.dylib or lipo -info, and update or reinstall the library. A missing symbol from a crate you wrote yourself usually means a function declared in an extern "C" block that no linked code defines.

A short procedure for any linking failure

  1. Scroll to the last = note: block and read only that.
  2. If the linker or compiler is missing, install a C toolchain.
  3. If it names -lfoo, install the development package for foo, or switch to a pure Rust crate.
  4. If the flags are unrecognized and you passed --target, configure a linker for that target.
  5. Run cargo build -v or add --verbose to see the full command when the omitted arguments matter.

The fourth step is also where a faster linker enters the picture. Configuring lld or mold to cut link time is a performance setting, not a fix, and if it is misconfigured it produces this same error with "linker 'clang' not found" or "cannot find ld.lld"; see Cargo profiles explained for what is safe to commit. When a linker is configured at the project level (in a workspace), remember that it applies to every contributor's machine.

When it is not a toolchain problem

If the same project links fine on a colleague's machine, compare environments instead of code: the OS version, the toolchain (rustc -vV), the installed libraries and any RUSTFLAGS or .cargo/config.toml in a parent directory. A project that links in CI but not locally, or the reverse, nearly always differs in one of these. And if the failing library is in a container build, check that the build stage and the runtime stage use compatible base images, an issue that comes up again in scratch images with musl.

Also inDevOps
Taggedlinker
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.