Rust: Toolchains vs Targets

2025/06/14 | Rust

Rust: Toolchains vs Targets

I’ve been using Rust for a year but never took the time to understand the difference between a toolchain and a target. For most people, this distinction isn’t necessary—but I work on UEFI, which is not a typical environment for Rust development.

I finally found time today to dig into this and understand the difference.

To get started, I make heavy use of Windows Sandbox to try things out. I launched Windows Sandbox and downloaded rustup-init.exe. When the installer runs, it detects the host operating system and architecture, and tries to install the stable-x86_64-pc-windows-msvc toolchain. The first part, stable-, refers to the channel (Rust has stable, beta, nightly, etc.), and the rest of the toolchain name is self-explanatory. This installs the relevant binaries to ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc

If we explore that directory, it becomes clearer:

├── bin
│   ├── cargo-clippy.exe
│   ├── cargo-fmt.exe
│   ├── cargo.exe                               <-- Cargo
│   ├── clippy-driver.exe
│   ├── rust-windbg.cmd
│   ├── rustc.exe                               <-- The main compiler
│   ├── rustc_driver-a71ba2c5a3b674ac.dll
│   ├── rustc_driver-a71ba2c5a3b674ac.pdb
│   ├── rustc_main-72a9ff7b4aa9ab57.pdb
│   ├── rustdoc.exe
│   ├── rustfmt.exe
│   ├── std-d7a86f21fcd377c7.dll
│   └── std-d7a86f21fcd377c7.pdb
├── etc
│   └── bash_completion.d
├── lib
│   └── rustlib                                <-- host built .rlibs will go here
├── libexec
│   └── rust-analyzer-proc-macro-srv.exe
└── share
    ├── doc
    ├── man
    └── zsh

Inside lib/rustlib:

lib/rustlib
├── components
├── etc
├── manifest-cargo-x86_64-pc-windows-msvc
├── manifest-clippy-preview-x86_64-pc-windows-msvc
├── manifest-rust-docs-x86_64-pc-windows-msvc
├── manifest-rust-src
├── manifest-rust-std-x86_64-pc-windows-msvc
├── manifest-rustc-x86_64-pc-windows-msvc
├── manifest-rustfmt-preview-x86_64-pc-windows-msvc
├── multirust-channel-manifest.toml
├── multirust-config.toml
├── rust-installer-version
└── x86_64-pc-windows-msvc                                     <-- The actual host target
    ├── bin                                                    <-- some more tools
    │   ├── gcc-ld
    │   │   ├── ld.lld.exe
    │   │   ├── ld64.lld.exe
    │   │   ├── lld-link.exe
    │   │   └── wasm-ld.exe
    │   └── rust-lld.exe
    └── lib
        ├── liballoc-c032859c81f4576b.rlib
        ├── libcfg_if-c91146a1b584a0a7.rlib
        ├── libcompiler_builtins-1f67c2a5a11a0b2e.rlib
        ├── libcore-dfdcb1635a201156.rlib                       <-- .rlib for lib core
        ├── libgetopts-1e0fa40794c35c34.rlib
        ├── libhashbrown-5e5ab7fb8d3e9a6b.rlib
        ├── libpanic_abort-c993e2b64d4e3e00.rlib
        ├── libpanic_unwind-97f6a0482881a03a.rlib
        ├── libproc_macro-71e312399c702b2a.rlib
        ├── libprofiler_builtins-9434e4a801ee8b5f.rlib
        ├── librustc_demangle-f8c4d6a2240f107f.rlib
        ├── librustc_std_workspace_alloc-7846558dfa99a578.rlib
        ├── librustc_std_workspace_core-628fee62996a202b.rlib
        ├── librustc_std_workspace_std-69744bf5a30d431b.rlib
        ├── libstd-d7a86f21fcd377c7.rlib
        ├── libstd_detect-803b4d5ce4fcd522.rlib                 <-- .rlib for lib std
        ├── libsysroot-5a1eb55846eb9586.rlib
        ├── libtest-afc8c579c7d286c1.rlib
        ├── libunicode_width-2cda9e7ff746aad4.rlib
        ├── libunwind-3adc2db30827f7fe.rlib
        ├── std-d7a86f21fcd377c7.dll
        ├── std-d7a86f21fcd377c7.dll.lib
        └── std-d7a86f21fcd377c7.pdb

So, when we run rustup-init.exe, it installs host-runnable .exe files and host-buildable .rlib files for the default target (x86_64-pc-windows-msvc). This is the default behavior.


Now, since I work on UEFI, I always run: rustup target add x86_64-unknown-uefi

This adds another directory under lib/rustlib:

lib/rustlib
├── components
├── manifest-cargo-x86_64-pc-windows-msvc
├── manifest-clippy-preview-x86_64-pc-windows-msvc
├── manifest-rust-docs-x86_64-pc-windows-msvc
├── manifest-rust-src
├── manifest-rust-std-x86_64-pc-windows-msvc
├── manifest-rust-std-x86_64-unknown-uefi
├── manifest-rustc-x86_64-pc-windows-msvc
├── manifest-rustfmt-preview-x86_64-pc-windows-msvc
├── multirust-channel-manifest.toml
├── multirust-config.toml
├── rust-installer-version
├── x86_64-pc-windows-msvc
│   ├── bin
│   │   ├── gcc-ld
│   │   │   ├── ld.lld.exe
│   │   │   ├── ld64.lld.exe
│   │   │   ├── lld-link.exe
│   │   │   └── wasm-ld.exe
│   │   └── rust-lld.exe
│   └── lib
│       ├── liballoc-c032859c81f4576b.rlib
│       ├── libcfg_if-c91146a1b584a0a7.rlib
│       ├── libcompiler_builtins-1f67c2a5a11a0b2e.rlib
│       ├── libcore-dfdcb1635a201156.rlib
│       ├── libgetopts-1e0fa40794c35c34.rlib
│       ├── libhashbrown-5e5ab7fb8d3e9a6b.rlib
│       ├── libpanic_abort-c993e2b64d4e3e00.rlib
│       ├── libpanic_unwind-97f6a0482881a03a.rlib
│       ├── libproc_macro-71e312399c702b2a.rlib
│       ├── libprofiler_builtins-9434e4a801ee8b5f.rlib
│       ├── librustc_demangle-f8c4d6a2240f107f.rlib
│       ├── librustc_std_workspace_alloc-7846558dfa99a578.rlib
│       ├── librustc_std_workspace_core-628fee62996a202b.rlib
│       ├── librustc_std_workspace_std-69744bf5a30d431b.rlib
│       ├── libstd-d7a86f21fcd377c7.rlib
│       ├── libstd_detect-803b4d5ce4fcd522.rlib
│       ├── libsysroot-5a1eb55846eb9586.rlib
│       ├── libtest-afc8c579c7d286c1.rlib
│       ├── libunicode_width-2cda9e7ff746aad4.rlib
│       ├── libunwind-3adc2db30827f7fe.rlib
│       ├── std-d7a86f21fcd377c7.dll
│       ├── std-d7a86f21fcd377c7.dll.lib
│       └── std-d7a86f21fcd377c7.pdb
└── x86_64-unknown-uefi                                 <-- add this new target directory
    └── lib
        ├── libaddr2line-fd4fe626dc9eac3b.rlib
        ├── libadler-b088dd35194f5782.rlib
        ├── liballoc-731136177e8ef7d9.rlib
        ├── libcfg_if-b1cd801320ee1d7a.rlib
        ├── libcompiler_builtins-ecc0e4d1597f6bb7.rlib
        ├── libcore-11582b155e3fe91e.rlib               <-- .rlib for lib core for uefi target
        ├── libgetopts-e754ef5dc9c65167.rlib
        ├── libgimli-5155ae5a196cfc77.rlib
        ├── libhashbrown-ebe15d8d60c1bd03.rlib
        ├── liblibc-df19c578f2e6ae8c.rlib
        ├── libmemchr-47d769e3d4e585ba.rlib
        ├── libminiz_oxide-960b5008cfaadc66.rlib
        ├── libobject-a0fa73a7d99f0471.rlib
        ├── libpanic_abort-e8a2aa9cca3ad7d9.rlib
        ├── libpanic_unwind-995a04acd82bab2e.rlib
        ├── libproc_macro-acf8bc6ecbc27bb3.rlib
        ├── libr_efi-2db192ae3744fca7.rlib
        ├── libr_efi_alloc-b2d3781ce8cb4558.rlib
        ├── librustc_demangle-26a32575c71c2df9.rlib
        ├── librustc_std_workspace_alloc-174dc4ffd730494d.rlib
        ├── librustc_std_workspace_core-def1fd15e4f4ae8a.rlib
        ├── librustc_std_workspace_std-8fd8317bcb8f6078.rlib
        ├── libstd-7dc783e751f67fda.rlib                <-- .rlib for lib std for uefi target
        ├── libstd_detect-9dc0888bef2c4a9f.rlib
        ├── libsysroot-8380d2aeed2b1e54.rlib
        ├── libtest-b27a33215908762d.rlib
        ├── libunicode_width-7eb3dd319612710b.rlib
        └── libunwind-729047b47ec9b476.rlib

Note that this new target is still added within the same toolchain path:~\.rustup\toolchains\stable-x86_64-pc-windows-msvc


Can I install other toolchains on Windows?

Before answering that, it’s helpful to know what toolchains are available.

See:

Rust classifies toolchains into tiers. Tier 1 platforms are officially supported and can run the Rust compiler natively. These include:

Now, back to the question—can we install, say, stable-aarch64-apple-darwin on a Windows machine?

The short answer: Yes, you can install it rustup toolchain install stable-aarch64-apple-darwin. But the long answer is: you can’t run the binaries in that toolchain (like rustc) on Windows because they’re not PE executables. rustup will even warn you:

C:\>rustup toolchain install stable-aarch64-apple-darwin
error: DEPRECATED: future versions of rustup will require --force-non-host to install a non-host toolchain.
warning: toolchain 'stable-aarch64-apple-darwin' may not be able to run on this system.
warning: If you meant to build software to target that platform, perhaps try `rustup target add aarch64-apple-darwin` instead?

Conclusion

To summarize: a toolchain refers to the rustc compiler that can run on the host, whereas a target refers to the Rust libraries used for cross-compiling to other platforms (including UEFI).

Channels (stable/beta/nightly)
└── Toolchains (x86_64-pc-windows-msvc / aarch64-apple-darwin)
    └── Targets (x86_64-pc-windows-msvc / x86_64-unknown-uefi / ...)

The available targets can be seen on the rustup components history page. More information about what Tier 1, Tier 2, Tier 2.5, and Tier 3 mean is explained on the platform support page.