How to Cross-Compile Rust for Windows from Linux

Add the Windows target with rustup and build your Rust project using the --target flag to generate a Windows executable from Linux.

You need a Windows binary. You are on Linux. You do not need a Windows machine.

You are coding on your favorite Linux distribution. The terminal is fast, the package manager is reliable, and your workflow is smooth. A request arrives: "Can you send me the Windows build?" or "The CI pipeline needs to produce an .exe." You do not have a Windows laptop. You do not want to dual-boot. You just want to produce a binary that runs on Windows from your Linux environment.

Rust handles this natively. The compiler does not care what operating system you run it on. It only cares about what operating system the output binary will run on. This separation of host and target is built into the toolchain. You can compile for Windows, macOS, ARM, WebAssembly, or embedded microcontrollers from a single Linux machine. The process is deterministic and repeatable.

Host, target, and the target triple

Rust distinguishes between the host and the target. The host is the machine running the compiler. The target is the platform where the resulting binary will execute. When you compile, you tell rustc which target to emit.

The target is specified as a "triple" string. This string encodes the architecture, the vendor, the operating system, and the ABI (Application Binary Interface). For Windows on 64-bit x86, you will see two common triples:

  • x86_64-pc-windows-gnu
  • x86_64-pc-windows-msvc

The first part x86_64 is the architecture. pc is the vendor (historically Intel/PC). windows is the OS. The final part is the ABI. gnu means the binary uses the MinGW toolchain conventions. msvc means it uses the Microsoft Visual C++ conventions.

Think of the compiler as a factory built on Linux soil. The factory has interchangeable molds for the product output. You can swap the mold to produce a widget that fits into a Windows machine. The factory adjusts the file format, the calling conventions, and the headers. The result is a binary that Windows understands, even though it was forged on Linux.

Minimal setup

You need two steps to cross-compile. First, install the target standard library. Second, build with the target flag.

# Install the Windows target standard library.
# This downloads rust-std for the target, not the compiler.
rustup target add x86_64-pc-windows-gnu

# Build the project for Windows.
# The --target flag tells cargo to emit a Windows binary.
cargo build --target x86_64-pc-windows-gnu --release

The build output lands in target/x86_64-pc-windows-gnu/release/. You will find a .exe file there. This file is a PE/COFF executable, the standard format for Windows. It will not run on Linux. It will run on Windows.

Convention aside: The community convention is to use cargo build --target explicitly. Some developers set environment variables like CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER to customize the linker, but the flag is the standard way to invoke cross-compilation. Stick to the flag for clarity.

What happens under the hood

When you run rustup target add, rustup downloads the rust-std component for that target. This is the standard library pre-compiled for Windows. It contains the core crates, the allocator, and the platform-specific implementations. You do not need to compile the standard library from source. The download is fast and cached.

When you run cargo build --target, cargo invokes rustc with the --target argument. rustc switches its code generation mode. It emits PE/COFF object files instead of ELF files. It adjusts the calling conventions to match the Windows ABI. It links against the Windows standard library.

For the gnu target, rustc invokes the MinGW linker. This linker knows how to produce Windows binaries on Linux. It stitches the object files together and produces the final .exe. If you are missing the MinGW linker, the build fails with a linker error.

Trust the toolchain. rustup and cargo handle the plumbing. Your job is to pick the right target and ensure the dependencies support it.

The GNU versus MSVC divide

This is where cross-compilation gets tricky. The two Windows targets are not interchangeable. They use different ABIs and different C runtimes.

The x86_64-pc-windows-gnu target uses MinGW. MinGW is a collection of GNU tools that produce Windows binaries. It is available on Linux via package managers. The resulting binary links dynamically to msvcrt.dll by default. This is the standard C runtime on Windows. The binary runs on any modern Windows system.

The x86_64-pc-windows-msvc target uses the MSVC ABI. This is the ABI used by Visual Studio. It requires the MSVC linker and libraries. On Linux, this is difficult. You need a cross-compiler that emits MSVC-compatible binaries. Such tools are rare and often require complex setup. The msvc target is the default on Windows, but it is not the default path for Linux cross-compilation.

Most Rust dependencies support both targets. Pure Rust code compiles identically for both. The difference appears when a dependency includes C code or assembly. A crate that uses openssl or ring may have build scripts that assume a specific toolchain. If the build script cannot find the MSVC cross-compiler, the build fails.

Convention aside: The community prefers gnu for Linux-to-Windows cross-compilation. The toolchain is available via apt install mingw-w64 on Debian/Ubuntu or pacman -S mingw-w64-gcc on Arch. The msvc target on Linux is reserved for advanced use cases where you have a specific reason to match the MSVC ABI.

Realistic example: dependencies and testing

Pure Rust projects cross-compile effortlessly. The moment you introduce dependencies that compile C code, you need to verify the toolchain.

Consider a project with serde and clap. Both are pure Rust. The build works out of the box.

[dependencies]
serde = { version = "1.0", features = ["derive"] }
clap = { version = "4.0", features = ["derive"] }
# Install MinGW linker if not present.
# This provides the cross-linker for the gnu target.
sudo apt install mingw-w64

# Build for Windows.
cargo build --target x86_64-pc-windows-gnu --release

Now consider a dependency like openssl-sys. This crate compiles OpenSSL from source. It requires a C compiler and linker for the target. On Linux, you need the MinGW C compiler.

# Install the MinGW C compiler.
# This allows build scripts to compile C code for Windows.
sudo apt install gcc-mingw-w64-x86-64

# Build again.
cargo build --target x86_64-pc-windows-gnu --release

If you skip the C compiler, the build script fails. The error comes from the build script, not rustc. You will see messages about missing cc or gcc.

Testing the binary: You cannot run the Windows binary on Linux directly. You need Wine. Wine is a compatibility layer that runs Windows executables on Linux.

# Install Wine.
sudo apt install wine64

# Run the Windows binary under Wine.
wine target/x86_64-pc-windows-gnu/release/my-app.exe

Wine bridges the gap. You can test the binary locally without a Windows machine. This is invaluable for CI/CD pipelines.

Convention aside: The community convention is to test cross-compiled binaries with Wine in CI. Tools like cargo-wine automate this. Run cargo wine run --target x86_64-pc-windows-gnu to build and run in one step. This saves time and reduces friction.

Pitfalls and compiler errors

Cross-compilation fails in predictable ways. The errors point to the root cause.

Missing target standard library: If you forget rustup target add, cargo cannot find the standard library. The compiler rejects you with E0463 (can't find crate for std). Install the target and retry.

Missing linker: If you build for gnu without MinGW, the linker fails. You see linking with cc failed or ld: cannot find -l.... Install mingw-w64 and retry.

C dependency without cross-compiler: If a crate compiles C code and you lack the cross-compiler, the build script fails. The error mentions cc or gcc not found. Install the MinGW C compiler.

MSVC target on Linux: If you try x86_64-pc-windows-msvc on Linux without a MSVC cross-compiler, the build fails. The linker cannot be found. Switch to gnu or set up a MSVC cross-toolchain.

E0463 for dependencies: If a dependency does not support the target, you may see E0463 for that crate. The crate failed to compile. Check the crate's documentation for target support. Some crates only support specific platforms.

E0308 mismatched types: This error can appear if a crate uses conditional compilation incorrectly. The code path for Windows may have a type mismatch. This is a bug in the crate or your code. Update the crate or fix the code.

Check your dependencies. Pure Rust is portable. C bindings are not. Verify every crate supports your target before you build.

Decision: which target and when

Choose the target based on your host OS and your dependency requirements.

Use x86_64-pc-windows-gnu when you are on Linux and want a working Windows binary with minimal setup. The toolchain is available via package managers. The binary runs on Windows. This is the default choice for Linux cross-compilation.

Use x86_64-pc-windows-msvc when your dependencies strictly require the MSVC ABI or you are building in a CI environment equipped with MSVC cross-compilation tools. This target is rare on Linux. Only use it if you have a specific reason.

Use cargo build --target explicitly in your workflow. Do not rely on environment variables for the target. The flag is clear and reproducible.

Use Wine to test the binary locally. Do not ship untested binaries. Wine catches runtime errors before they reach the user.

Install MinGW tools on your Linux machine. The linker and C compiler are prerequisites for most projects. Do not skip this step.

Where to go next