Rust in WSL: rustup Toolchains, Linux Linkers, and Target Artifacts
Keep Rust toolchains and Cargo artifacts native to WSL, then add target-specific linkers deliberately for reliable cross-compilation.
Rust’s rustup makes it easy to install multiple compiler toolchains and compilation targets, but a target being listed by rustup does not mean that every project can be linked for it. A target standard library may be available while a linker, C runtime, SDK, or native dependency is still missing. WSL is a Linux host for the toolchain; it is not a Windows Rust environment merely because the terminal is shown in a Windows application.
The practical goal is to keep rustc, Cargo, source, caches, and Linux linkers on the Linux side, then add a separate and explicit cross toolchain for each non-host target. This avoids mixing executable outputs and Linux ELF artifacts in one uncontrolled target directory.
Establish the WSL toolchain identity
Install Rust through the official rustup installation path inside the distribution. The official Rust site explicitly provides the rustup command for WSL users. After installation, open a new shell and inspect:
command -v rustc cargo rustup
rustc --version --verbose
cargo --version
rustup show
rustup target list --installed
These commands identify the active toolchain, its host triple, and the standard libraries installed for compilation targets. A target added with rustup is not the same as a configured linker. Check executable paths so a Windows Rust installation under an interop-mounted path is not silently selected.
Keep ~/.cargo and the project checkout inside the WSL filesystem. Cargo’s registry and git caches contain source and build data that Linux tools can manage, while the project’s target directory includes host-specific build products. Do not share one Cargo home or target tree between Windows and Linux toolchains.
Pin the project’s compiler policy
A repository can use rust-toolchain.toml to select a channel, version, and components. That file should reflect a deliberate support decision; a developer should not switch to nightly merely because a stable build failed. Check the active selection from the project directory:
rustup show active-toolchain
rustc --version
cargo metadata --no-deps --format-version 1
Commit Cargo.lock for applications and binaries when the project intends reproducible dependency resolution. Libraries may use a different lockfile policy. The lockfile records Rust package graph choices; it does not pin system linkers, libc versions, C headers, or the WSL kernel.
Install components such as rustfmt or Clippy only when the repository’s checks require them. Their versions should follow the selected toolchain policy. A host that builds without linting is not equivalent to CI if CI runs cargo fmt –check and Clippy.
Build and test for the Linux host
For an ordinary Linux target, the default host toolchain usually needs the distro’s C build essentials and any project-specific libraries:
cd ~/src/worker
rustup show
cargo fmt --check
cargo test --locked
cargo build --release --locked
file target/release/worker
Use the distro package manager to install the supported native compiler and libraries. Native dependencies may require development headers and a pkg-config entry. Read the first linker error and identify the missing package instead of adding unrelated environment variables.
The example uses –locked so Cargo fails rather than rewriting a committed lockfile. If the repository does not commit a lockfile by policy, remove that flag and follow the project’s documented resolution rules. Do not change lockfile policy in a troubleshooting command.
Add cross-compilation targets deliberately
Rust’s target support has several layers. First add the target standard library if rustup provides it:
rustup target add aarch64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu
That command may fail at linking if no compiler for the target is configured. For GNU/Linux cross-compilation, a target linker and compatible C sysroot are normally needed. Configure Cargo’s target-specific linker in a project or user configuration after installing a toolchain that matches the target ABI. Do not point an ARM target at the host x86-64 linker.
For Windows GNU or MSVC targets, the required linker and system libraries differ. A Linux-hosted WSL toolchain and a Windows-hosted rustup installation are not substitutes for one another. Pick the build host and target deliberately, then test the produced artifact in an environment that matches its target.
Cargo supports target-specific linker settings. Keep them scoped to the target rather than exporting a linker for every Rust build:
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
The linker name is an example, not an automatic dependency. Install a cross compiler and sysroot that provide that command, verify it with command -v, and confirm that its output ABI matches the target. A target-specific setting prevents an ARM linker from unexpectedly affecting the host’s ordinary x86-64 builds.
If the project uses crates with native dependencies, verify whether their build scripts honor the configured target linker and associated compiler variables. Some libraries need a target-aware pkg-config setup or explicit include and library directories. A successful Rust standard-library compile cannot supply missing C headers or target system libraries.
Keep native crates and build scripts visible
Some crates compile C or C++ code, run build scripts, or discover system libraries through environment variables. Such a crate may build natively in WSL and still fail for a cross target because its build script executes on the host while producing target code. The build script’s host/target distinction matters; read its documentation and configure the cross compiler it expects.
Record relevant environment overrides such as CC, CXX, and PKG_CONFIG_PATH in controlled build configuration rather than a user’s global shell profile. Verify the compiler binary, target triple, and discovered library paths before release. Never set a target linker globally when one repository needs it; another project may build a different architecture.
Diagnose Cargo cache and output confusion
If a dependency compiles on Windows but fails in WSL, remove only the affected project’s generated target directory after preserving any needed artifacts, then rebuild on Linux. Cargo can reuse compatible build outputs, but the host OS, compiler, feature set, and target all influence them.
rustc --version --verbose
rustup show
cargo tree --locked
cargo build --release --locked
Do not delete ~/.cargo/registry as a first response. It is a cache, and deleting it mainly forces downloads. Prefer a clean build of the project’s target artifacts and inspect package resolution before altering the shared registry cache.
If a linker fails, separate Rust compilation from native linking. A compiler error in Rust source, unavailable crate, missing host dependency, and missing target linker are different failure classes. Keep the full command and first diagnostic line with the build record.
Cargo workspaces add another dimension: a root manifest may define multiple packages with different features and build scripts. Run the same workspace selection and feature flags used by CI; testing only the current crate can miss a sibling package that owns a native dependency. Record the exact command, target triple, and feature set, because Cargo feature unification can alter what gets compiled.
Acceptance tests for a release target
For each supported target, document the rustup toolchain, target triple, linker and sysroot, distro, native dependencies, and whether tests ran on the target. Run unit tests on the WSL host for host code. Cross-compiling tests may produce a target executable that cannot run on the host; use a compatible runner or emulator if the release requires that coverage.
Check the artifact with platform-appropriate inspection tools and execute a smoke test in a matching target environment. A successful cargo build –target proves compilation and linking only; it does not prove a Windows binary is signed, an embedded image boots, or a Linux program works on every libc.
The clean boundary is simple: rustup manages compiler channels and standard libraries; Cargo resolves Rust packages and creates build artifacts; the OS toolchain supplies target linkers and C dependencies; the acceptance environment proves runtime behavior.
Related:
- WSL on Windows Arm: Matching the Host, Kernel, Distro, and Packages
- WSL, Dev Drive, and Filesystem Placement: Choosing Performance Without Losing Interop
Sources: