# Rust Development Windows Native Setup Adding Rust development on top of a Windows 11 machine already configured per [[General_Development_Windows_Native_Setup]]. Most of the stack carries over — Windows Terminal, PowerShell 7, Starship, Git, GitHub CLI, VS Code, Claude Code, and the PSReadLine setup all work identically. This guide only covers the Rust-specific additions, and everything installs the same way on **ARM64 (Snapdragon, Surface Pro 11, Dev Kit 2023) and x64 (Intel/AMD)**. It's the Windows counterpart to [[Rust_Development_Ubuntu_Setup]] and [[Rust_Development_Mac_Tahoe_Setup]]. > [!tip] AI assistance is optional > The reference to Claude Code above assumes you completed Part 7 of the General guide. If you skipped it or use a different AI tool, this guide's instructions still work — the Rust toolchain doesn't depend on any AI being present. > [!info] Coexistence > C, Python, Ruby, Go, and Rust can all live on the same Windows machine without interfering. Each language's toolchain installs to its own prefix: `uv` under `%USERPROFILE%\AppData\Roaming\uv\`, Ruby under its installer's directory, Go under `%USERPROFILE%\go\`, Rust under `%USERPROFILE%\.rustup\` and `%USERPROFILE%\.cargo\`, and the MSVC C toolchain under `C:\Program Files\Microsoft Visual Studio\2026\BuildTools\`. No conflicts. --- ## Prerequisite: a C linker (MSVC Build Tools) This is the **big one** for Rust on Windows. Unlike Linux (where `cc` is one apt package away) or macOS (where the Xcode Command Line Tools cover it), Windows doesn't ship a system C linker out of the box. Rust's default and recommended toolchain on Windows is `*-pc-windows-msvc`, which links against the Microsoft Visual C++ runtime and uses Microsoft's `link.exe`. Without it, `cargo build` fails immediately with: ``` error: linker `link.exe` not found note: the msvc targets depend on the msvc linker but `link.exe` was not found note: please ensure that Visual Studio 2026 Build Tools were installed with the Visual C++ option ``` You need **Visual Studio 2026 Build Tools** with the **"Desktop development with C++"** workload and the **Windows 11 SDK**. The Build Tools are the standalone command-line subset of Visual Studio — you do *not* need the full Visual Studio IDE. ### Install via winget The Visual Studio installer is itself a winget package, and it accepts `--override` to pass workload arguments through to the inner installer. Run from an **elevated PowerShell** (right-click Windows Terminal → "Run as administrator"): ```powershell winget install -e --id Microsoft.VisualStudio.2026.BuildTools ` --override "--passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended" ``` Flag breakdown: | Argument | What it does | |----------|--------------| | `--passive` | Show installer progress UI but require no clicks | | `--add Microsoft.VisualStudio.Workload.VCTools` | The "Desktop development with C++" workload | | `--add Microsoft.VisualStudio.Component.Windows11SDK.26100` | The Windows 11 SDK (24H2 build) | | `--includeRecommended` | Pull in the recommended sub-components for the workload (the C++ build tools, MSBuild, CMake support, etc.) | This is several gigabytes and takes 10–20 minutes depending on disk and network. You can keep working — the installer downloads and stages in the background. > [!tip] Same install as the C guide > This is **the same Visual Studio Build Tools install** that the [[C_Development_Windows_Native_Setup]] guide uses. If you've already followed the C guide on this machine, the linker is already in place — skip this section and jump straight to [Install Rust via rustup](#install-rust-via-rustup). ### Verify the linker is installed The Build Tools install adds a Start Menu entry called **"Developer PowerShell for VS 2026"**. Open one — it's a PowerShell session with the MSVC environment variables (`PATH`, `INCLUDE`, `LIB`, etc.) pre-loaded. Confirm the linker is found: ```powershell where.exe link.exe # Should print something like: # C:\Program Files\Microsoft Visual Studio\2026\BuildTools\VC\Tools\MSVC\14.40.33807\bin\Hostx64\x64\link.exe ``` > [!info] You don't need to live in the Developer PowerShell > rustup and cargo auto-locate the MSVC toolchain through the Windows registry — once it's installed anywhere on the system, `cargo build` works from a normal PowerShell tab. The Developer PowerShell is only useful here to **prove the install worked**. After that, go back to your regular PowerShell 7 profile. > [!warning] ARM64 hosts: build tools install as native ARM64 > The VS 2026 Build Tools have native ARM64 binaries (since VS 2022 17.10 in 2024). `link.exe` runs natively, cross-compiles to both ARM64 and x64 targets, and `cargo build --target x86_64-pc-windows-msvc` from an ARM64 host produces native x64 binaries. No emulation overhead during builds. --- ## Install Rust via rustup **Use rustup, not the bare `rustc.exe` from another package source.** The Rust team maintains rustup as the canonical toolchain manager; it's the only path that gives you painless toolchain switching, target installation, and component management (`rustfmt`, `clippy`). ### Option A — winget (recommended) ```powershell winget install -e --id Rustlang.Rustup ``` This pulls a native binary for your architecture, installs rustup itself, then **launches `rustup-init` interactively**. Accept the default ("Proceed with standard installation") unless you have a specific reason to customize. rustup will install: - `rustc` — the compiler - `cargo` — the package manager and build tool - `rustup` — the toolchain manager (multiple Rust versions, stable/nightly switching) - `rustfmt` — the formatter - `clippy` — the linter - The native MSVC toolchain for your architecture Everything lands in `%USERPROFILE%\.cargo\` and `%USERPROFILE%\.rustup\`. ### Option B — `rustup-init.exe` from rustup.rs If you'd rather grab the installer directly: 1. Browse to <https://rustup.rs> 2. Download `rustup-init.exe` (the page auto-detects ARM64 vs x64 and serves the matching binary) 3. Run it from a PowerShell window Same result, same defaults. The website's installer is the official Rust Foundation distribution. > [!info] rustup 1.28+ supports ARM64 as a native host > rustup itself, as well as the `aarch64-pc-windows-msvc` host triple, are both native on ARM64 Windows as of rustup 1.28 (released late 2024). You get a native rustup binary, a native rustc, and a native cargo — no x64 emulation in the toolchain. ARM64 builds of crates link natively against ARM64 system libraries. ### Confirm the default host triple After `rustup-init` finishes, open a **new** PowerShell window (so `%USERPROFILE%\.cargo\bin` is on the PATH) and verify: ```powershell rustup show ``` You should see: - On ARM64: `Default host: aarch64-pc-windows-msvc` - On x64: `Default host: x86_64-pc-windows-msvc` If you accidentally landed on the `*-pc-windows-gnu` toolchain (sometimes happens if a previous install configured it), switch: ```powershell # Switch the default toolchain to MSVC rustup default stable-msvc # Add the matching target for cross-compiles or sanity rustup target add aarch64-pc-windows-msvc # on ARM64 hosts rustup target add x86_64-pc-windows-msvc # on x64 hosts (or for cross-builds) ``` > [!info] MSVC vs GNU on Windows — why MSVC is the default > The MSVC toolchain links against the standard Windows runtime (`vcruntime`, `ucrt`) and produces binaries that behave like every other Windows program — they integrate with Visual Studio debuggers, Windows error reporting, ETW, and crash dumps. The GNU toolchain (MinGW) is useful in narrow situations (cross-compiling from Linux, certain legacy C interop), but on a Windows host you want MSVC. The Rust team and rustup both default to MSVC, and every major Rust GUI/system library is tested against it. ### `%USERPROFILE%\.cargo\bin` on PATH `rustup-init` (whether launched by winget or the standalone installer) **automatically prepends `%USERPROFILE%\.cargo\bin` to your user PATH** via the Windows registry. The current PowerShell window won't see the change — open a new tab. Verify: ```powershell rustc --version cargo --version rustup --version ``` > [!info] PowerShell sees PATH changes in new tabs only > winget and rustup write the new PATH entry to the user's environment registry (`HKCU\Environment`). Existing PowerShell windows cached the old PATH at startup and won't pick it up. Opening a new Terminal tab is the simplest fix — or run `$env:PATH = [System.Environment]::GetEnvironmentVariable('Path','User') + ';' + [System.Environment]::GetEnvironmentVariable('Path','Machine')` in the current tab to refresh manually. ### Updating Rust ```powershell # Update all installed toolchains rustup update # Update only nightly (if you have it installed) rustup update nightly # Show what's installed and what the active toolchain is rustup show ``` ### Installing additional toolchains ```powershell # Install the nightly channel alongside stable rustup toolchain install nightly # Run cargo against a specific toolchain just for one command rustup run nightly cargo build # Pin a directory to a non-default toolchain rustup override set nightly ``` --- ## Global Rust tooling Install these once — they're used across all Rust projects. Each compiles from source against your native architecture and lands in `%USERPROFILE%\.cargo\bin\`: ```powershell # Watches files, reruns tests/build cargo install cargo-watch # Dependency security audit (checks against RustSec advisories) cargo install cargo-audit # Faster parallel test runner cargo install cargo-nextest # cargo-edit — adds `cargo upgrade` and more (`cargo add` / `cargo rm` are now built-in) cargo install cargo-edit # cargo-outdated — shows outdated deps cargo install cargo-outdated # All in one command (and forced to use the locked dep versions in each crate) cargo install --locked cargo-watch cargo-audit cargo-nextest cargo-edit cargo-outdated ``` The `--locked` flag tells `cargo install` to honour each crate's `Cargo.lock` rather than resolving fresh dependencies — slower to update but produces the exact build the tool authors tested. > [!info] These compile from source > `cargo install` builds each tool from source for your architecture, so each takes 1–3 minutes. That's expected — the upside is they're always current and optimized for your CPU (including native ARM64 NEON code paths where the crate supports them). > [!info] `rustfmt` and `clippy` are already installed > Both ship as default rustup components. You don't need to `cargo install` them. To confirm: `rustup component list --installed`. To add a missing one (rare): `rustup component add rustfmt clippy`. --- ## VS Code extensions for Rust ```powershell # The official rust-analyzer language server code --install-extension rust-lang.rust-analyzer # CodeLLDB — debugger that works natively on Windows (ARM64 + x64) code --install-extension vadimcn.vscode-lldb # crates — shows latest versions of crates in Cargo.toml code --install-extension serayuzgur.crates # Even Better TOML (you may already have it from the General guide) code --install-extension tamasfe.even-better-toml ``` > [!info] rust-analyzer vs the old "Rust" extension > The extension named just "Rust" (RLS-based) is deprecated. `rust-analyzer` (published by `rust-lang.rust-analyzer`) replaced it years ago and is the official choice. > [!info] CodeLLDB on Windows > CodeLLDB bundles its own LLDB build (no system install required) and works against MSVC-produced binaries via the MSVC debug info that rustc emits by default. For deeper Windows-specific debugging (ETW traces, Watson dumps), the Microsoft C/C++ extension's `cppvsdbg` adapter is an alternative — but CodeLLDB is faster to set up and works the same way it does on Mac/Linux, which is why every cross-platform Rust guide uses it. --- ## VS Code settings Append to your existing `settings.json` at `%APPDATA%\Code\User\settings.json` (open via `Ctrl+Shift+P` → "Preferences: Open User Settings (JSON)"): ```json { // ── Rust ────────────────────────────────────────────────── "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, "editor.tabSize": 4, "editor.rulers": [100] }, "rust-analyzer.check.command": "clippy", "rust-analyzer.cargo.features": "all", "rust-analyzer.inlayHints.chainingHints.enable": true, "rust-analyzer.inlayHints.parameterHints.enable": true, "rust-analyzer.inlayHints.typeHints.enable": true, "rust-analyzer.inlayHints.closureReturnTypeHints.enable": "always", "rust-analyzer.lens.enable": true, "rust-analyzer.lens.run.enable": true, "rust-analyzer.lens.debug.enable": true, "rust-analyzer.hover.actions.enable": true } ``` > [!info] `rust-analyzer.check.command: "clippy"` > By default, rust-analyzer uses `cargo check` for diagnostics. Switching to `clippy` runs the linter continuously, so you get style and correctness warnings inline as you type. The cost is a touch more CPU on save — usually unnoticeable on modern hardware. --- ## Full demo: A calculator program This walks through building, testing, debugging, and publishing a four-function calculator from scratch. Parallel implementations exist in C, Python, Ruby, and Go. ### Create the project ```powershell cd $HOME\projects cargo new --lib calc-rust cd calc-rust # Add a binary target directory for the CLI/GUI mkdir src\bin -ErrorAction SilentlyContinue ``` ### `Cargo.toml` ```toml [package] name = "calc" version = "0.1.0" edition = "2021" [lib] name = "calc" path = "src/lib.rs" [[bin]] name = "calc" path = "src/bin/calc.rs" [[bin]] name = "calc-gui" path = "src/bin/calc_gui.rs" [dependencies] # (GUI dep added in the GUI section) [dev-dependencies] ``` ### `src/lib.rs` — the core library ```rust //! Four-function calculator library. use std::fmt; #[derive(Debug, PartialEq)] pub enum CalcError { DivideByZero, UnknownOperator(String), ParseError(String), } impl fmt::Display for CalcError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CalcError::DivideByZero => write!(f, "division by zero"), CalcError::UnknownOperator(op) => write!(f, "unknown operator '{op}'"), CalcError::ParseError(s) => write!(f, "cannot parse '{s}' as a number"), } } } impl std::error::Error for CalcError {} pub fn add(a: f64, b: f64) -> f64 { a + b } pub fn sub(a: f64, b: f64) -> f64 { a - b } pub fn mul(a: f64, b: f64) -> f64 { a * b } pub fn div(a: f64, b: f64) -> Result<f64, CalcError> { if b == 0.0 { Err(CalcError::DivideByZero) } else { Ok(a / b) } } /// Dispatch to the appropriate operation based on `op`. /// Supported operators: "+", "-", "*", "x", "/". pub fn calculate(a: f64, op: &str, b: f64) -> Result<f64, CalcError> { match op { "+" => Ok(add(a, b)), "-" => Ok(sub(a, b)), "*" | "x" => Ok(mul(a, b)), "/" => div(a, b), _ => Err(CalcError::UnknownOperator(op.to_string())), } } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(calculate(6.0, "+", 7.0), Ok(13.0)); } #[test] fn test_sub() { assert_eq!(calculate(10.0, "-", 3.0), Ok(7.0)); } #[test] fn test_mul_star() { assert_eq!(calculate(4.0, "*", 5.0), Ok(20.0)); } #[test] fn test_mul_x() { assert_eq!(calculate(4.0, "x", 5.0), Ok(20.0)); } #[test] fn test_div() { assert_eq!(calculate(20.0, "/", 4.0), Ok(5.0)); } #[test] fn test_div_by_zero() { assert_eq!(calculate(5.0, "/", 0.0), Err(CalcError::DivideByZero)); } #[test] fn test_unknown_operator() { assert!(matches!( calculate(1.0, "?", 2.0), Err(CalcError::UnknownOperator(_)) )); } #[test] fn test_operations_table() { let cases: &[(f64, &str, f64, f64)] = &[ (1.0, "+", 1.0, 2.0), (0.0, "+", 0.0, 0.0), (-1.0, "+", 1.0, 0.0), (1.5, "+", 2.5, 4.0), (10.0, "-", 20.0, -10.0), (3.0, "*", 4.0, 12.0), ]; for (a, op, b, expected) in cases { assert_eq!( calculate(*a, op, *b), Ok(*expected), "calculate({a}, {op:?}, {b}) should equal {expected}" ); } } } ``` ### `src/bin/calc.rs` — the CLI ```rust //! Command-line entry point for the four-function calculator. use std::env; use std::process::ExitCode; use calc::calculate; fn usage(prog: &str) { eprintln!("Usage: {prog} <number> <op> <number>"); eprintln!(" op: + - * /"); eprintln!("Example: {prog} 6 + 7"); } fn main() -> ExitCode { let args: Vec<String> = env::args().collect(); if args.len() != 4 { usage(&args[0]); return ExitCode::from(1); } let a: f64 = match args[1].parse() { Ok(v) => v, Err(_) => { eprintln!("Error: cannot parse '{}' as a number", args[1]); return ExitCode::from(1); } }; let b: f64 = match args[3].parse() { Ok(v) => v, Err(_) => { eprintln!("Error: cannot parse '{}' as a number", args[3]); return ExitCode::from(1); } }; match calculate(a, &args[2], b) { Ok(result) => { // Print as integer if it came out whole if result == (result as i64) as f64 { println!("{}", result as i64); } else { println!("{result}"); } ExitCode::SUCCESS } Err(e) => { eprintln!("Error: {e}"); ExitCode::from(1) } } } ``` ### `.gitignore` Cargo generates a minimal one. Append if needed: ``` target/ ``` > [!info] `Cargo.lock` — commit it? > For **binary** crates (you distribute compiled executables), commit `Cargo.lock` for reproducible builds. For **library** crates, don't. This project is a binary, so keep it in git. ### Build and run ```powershell # Run the CLI (debug build, fast to compile) cargo run --bin calc -- 6 + 7 cargo run --bin calc -- 10 - 3 cargo run --bin calc -- 4 x 5 cargo run --bin calc -- 20 / 4 # Release build (optimized) cargo build --release .\target\release\calc.exe 6 + 7 # Install to %USERPROFILE%\.cargo\bin\ so `calc` is callable anywhere cargo install --path . --bin calc # Run tests cargo test # Or with nextest for faster parallel runs cargo nextest run # Lint cargo clippy -- -D warnings # Format all files in place cargo fmt # Full quality gate (common in CI) cargo fmt --check; cargo clippy -- -D warnings; cargo test ``` > [!info] `target\debug\` and `target\release\` > Windows places executables in `target\debug\calc.exe` and `target\release\calc.exe` — note the `.exe` extension that `cargo` adds automatically on Windows. The PowerShell syntax `.\target\release\calc.exe` runs an executable from the current directory (the leading `.\` is required, unlike on Linux/Mac where `./` is just a convention). ### Debug in VS Code With `vadimcn.vscode-lldb` installed, CodeLens appears above each `fn main()` and each `#[test]` with "Run | Debug" buttons. Click **Debug** to step through. For the CLI with arguments, create `.vscode\launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug calc CLI", "type": "lldb", "request": "launch", "cargo": { "args": ["build", "--bin=calc"], "filter": { "name": "calc", "kind": "bin" } }, "args": ["6", "+", "7"], "cwd": "${workspaceFolder}" }, { "name": "Debug unit tests", "type": "lldb", "request": "launch", "cargo": { "args": ["test", "--no-run", "--lib"], "filter": { "name": "calc", "kind": "lib" } }, "args": [], "cwd": "${workspaceFolder}" } ] } ``` Set a breakpoint, hit **F5**, step through native code with full variable inspection. CodeLLDB bundles its own LLDB build that understands MSVC-emitted debug info, so this works against the standard Rust toolchain on Windows without any extra debugger install. ### Publish to GitHub ```powershell git init git add . git commit -m "Initial commit: four-function calculator" gh repo create calc-rust --private --source=. --remote=origin --push ``` ### `CLAUDE.md` for this project ```markdown # Project: calc-rust ## Purpose Four-function command-line calculator in Rust — pedagogical example. ## Conventions - Rust 2021 edition, stable toolchain - Library (src/lib.rs) + binaries (src/bin/) - Tests colocated with code via #[cfg(test)] mod tests - Format with rustfmt (default style) - Lint with clippy — treat warnings as errors in CI ## Commands - Run CLI: `cargo run --bin calc -- <a> <op> <b>` - Run GUI: `cargo run --bin calc-gui` - Build release: `cargo build --release` - Install: `cargo install --path . --bin calc` - Test: `cargo test` (or `cargo nextest run` for parallel) - Lint: `cargo clippy -- -D warnings` - Format: `cargo fmt` - Quality gate: `cargo fmt --check; cargo clippy -- -D warnings; cargo test` ## Style - 4-space indent, 100-column line length - snake_case for functions and variables, CamelCase for types - Return `Result<T, E>` with a concrete error enum for recoverable errors - Use `#[derive(Debug, PartialEq)]` on public types where possible - Doc comments (`///`) on every public item ``` --- ## Full demo: A calculator GUI Same calculator, wrapped in a graphical interface. This uses **iced**, a pure-Rust, Elm-inspired GUI toolkit — the most popular Rust GUI library in 2026. ### Why iced? | Option | Pros | Cons | |--------|------|------| | **iced** | Pure Rust, Elm architecture (predictable state), native-feeling, actively developed | Still pre-1.0 (API shifts between versions) | | **egui** | Immediate-mode, great for tools/debug UIs, very simple | Custom widget look | | **Slint** | Declarative DSL, compiles to native, excellent perf | Custom language (`.slint` files) | | **Tauri** | HTML/JS frontend + Rust backend | Bundles a webview | | **WinUI 3 / windows-rs** | First-party Windows UI, native look | Windows-only, steeper Rust ergonomics | For a pedagogical calculator in Rust, iced is the right default. The Elm architecture (Model + Update + View) makes the state machine obvious and unit-testable. ### System dependencies for iced This is where Windows is dramatically simpler than Linux. iced renders through `wgpu` (which on Windows uses **DirectX 12** by default) and uses `winit` for windowing. **Windows ships DirectX 12 and the windowing APIs as part of the OS** — no `pkg-config`, no `libfontconfig1-dev`, no `libxkbcommon-dev`, no Mesa to install. If you completed the [Prerequisite: a C linker](#prerequisite-a-c-linker) section above, you already have everything iced needs: the MSVC linker, the Windows SDK headers, and DirectX. `cargo run --bin calc-gui` Just Works™ on both ARM64 and x64. > [!info] wgpu → DirectX 12 on Windows > wgpu auto-selects DX12 on Windows (it'll fall back to Vulkan or GL if explicitly requested), so the GPU pipeline matches what every native Windows game uses. ARM64 Windows ships DX12 drivers for Qualcomm Adreno; x64 Windows ships them for Intel, AMD, and NVIDIA. No driver gymnastics required. ### Add iced as a dependency ```powershell cargo add iced ``` Or pin it in `Cargo.toml`: ```toml [dependencies] iced = "0.13" ``` ### `src/bin/calc_gui.rs` — the GUI ```rust //! Graphical four-function calculator using iced. use iced::widget::{button, column, container, row, text, text_input}; use iced::{Element, Length, Task, Theme}; use calc::calculate; pub fn main() -> iced::Result { iced::application("Calculator", Calculator::update, Calculator::view) .theme(|_| Theme::Dark) .window_size((260.0, 360.0)) .run() } #[derive(Debug, Clone)] pub enum Message { Press(String), Clear, } #[derive(Default)] pub struct Calculator { current: String, stored: Option<f64>, pending_op: Option<String>, display: String, } impl Calculator { pub fn new() -> Self { Self { display: "0".to_string(), ..Default::default() } } pub fn update(&mut self, message: Message) -> Task<Message> { match message { Message::Press(label) => self.handle_press(&label), Message::Clear => self.clear(), } Task::none() } pub fn handle_press(&mut self, label: &str) { match label { d if d.chars().all(|c| c.is_ascii_digit() || c == '.') => { self.current.push_str(d); self.display = self.current.clone(); } "+" | "-" | "*" | "/" => { self.apply_pending(); self.pending_op = Some(label.to_string()); } "=" => { self.apply_pending(); self.pending_op = None; } _ => {} } } pub fn clear(&mut self) { self.current.clear(); self.stored = None; self.pending_op = None; self.display = "0".to_string(); } fn apply_pending(&mut self) { if self.current.is_empty() { return; } let value: f64 = match self.current.parse() { Ok(v) => v, Err(_) => { self.display = "Error".to_string(); self.reset_internal(); return; } }; let new_value = match (self.stored, self.pending_op.as_deref()) { (None, _) | (_, None) => value, (Some(s), Some(op)) => match calculate(s, op, value) { Ok(v) => v, Err(_) => { self.display = "Error".to_string(); self.reset_internal(); return; } }, }; self.stored = Some(new_value); // Format: integer if whole self.display = if new_value == (new_value as i64) as f64 { (new_value as i64).to_string() } else { format!("{new_value}") }; self.current.clear(); } fn reset_internal(&mut self) { self.current.clear(); self.stored = None; self.pending_op = None; } pub fn view(&self) -> Element<Message> { let display = text_input("0", &self.display) .size(28) .align_x(iced::alignment::Horizontal::Right); let btn = |label: &str| -> Element<Message> { button(text(label).size(18).center()) .width(Length::Fill) .padding(12) .on_press(Message::Press(label.to_string())) .into() }; let grid = column![ row![btn("7"), btn("8"), btn("9"), btn("/")].spacing(5), row![btn("4"), btn("5"), btn("6"), btn("*")].spacing(5), row![btn("1"), btn("2"), btn("3"), btn("-")].spacing(5), row![btn("0"), btn("."), btn("="), btn("+")].spacing(5), ] .spacing(5); let clear = button(text("Clear").center()) .width(Length::Fill) .padding(10) .on_press(Message::Clear); container( column![display, grid, clear] .spacing(10) .padding(10), ) .width(Length::Fill) .height(Length::Fill) .into() } } #[cfg(test)] mod tests { use super::*; fn press_sequence(presses: &[&str]) -> Calculator { let mut c = Calculator::new(); for p in presses { c.handle_press(p); } c } #[test] fn initial_display() { let c = Calculator::new(); assert_eq!(c.display, "0"); } #[test] fn single_digit() { let c = press_sequence(&["5"]); assert_eq!(c.display, "5"); } #[test] fn multi_digit() { let c = press_sequence(&["1", "2", "3"]); assert_eq!(c.display, "123"); } #[test] fn simple_addition() { let c = press_sequence(&["6", "+", "7", "="]); assert_eq!(c.display, "13"); } #[test] fn chained_operations() { // 2 + 3 * 4 → (2+3)*4 = 20 (left-to-right) let c = press_sequence(&["2", "+", "3", "*", "4", "="]); assert_eq!(c.display, "20"); } #[test] fn division() { let c = press_sequence(&["2", "0", "/", "4", "="]); assert_eq!(c.display, "5"); } #[test] fn division_by_zero() { let c = press_sequence(&["5", "/", "0", "="]); assert_eq!(c.display, "Error"); } #[test] fn clear_resets_state() { let mut c = press_sequence(&["5", "+", "3"]); c.clear(); assert_eq!(c.display, "0"); assert!(c.current.is_empty()); assert!(c.stored.is_none()); assert!(c.pending_op.is_none()); } #[test] fn decimal_input() { let c = press_sequence(&["1", ".", "5", "+", "2", ".", "5", "="]); assert_eq!(c.display, "4"); } } ``` ### Run everything ```powershell # CLI cargo run --bin calc -- 6 + 7 # GUI (first build is slow — iced pulls in winit, wgpu, etc.) cargo run --bin calc-gui # All tests (library + GUI state machine) cargo test # Release builds cargo build --release .\target\release\calc-gui.exe ``` The first iced build is slow (several minutes) because it compiles the whole graphics stack — winit for windowing, wgpu for rendering, plus the DirectX 12 bindings. Subsequent builds are cached and fast. > [!info] Shipping iced apps as installers > For a distributable Windows package, the simplest route is `cargo bundle`: > ```powershell > cargo install cargo-bundle > cargo bundle --release > ``` > This produces an `.msi` under `target\release\bundle\msi\`. For richer installer behavior (start menu shortcuts, signed binaries, auto-update), look at `cargo-wix` (`cargo install cargo-wix`; `cargo wix init`) which generates a WiX-based installer, or use `tauri-bundler` standalone even for non-Tauri apps. --- ## Starship prompt — Rust auto-detection The general Starship config from [[General_Development_Windows_Native_Setup]] already includes the `[rust]` module. When you `cd` into this project, the prompt shows: ``` ~\projects\calc-rust main 1.86.0 ❯ ``` Reading left-to-right: directory, git branch, Rust version. --- ## Installing CLI tools from other Rust projects `cargo install` is the Rust equivalent of `uv tool install` or `go install` — it installs binaries globally (to `%USERPROFILE%\.cargo\bin\`) from crates.io or a git URL: ```powershell # Install ripgrep from crates.io (you may already have it from the General guide) cargo install ripgrep # Install tokei (fast code counter) cargo install tokei # Install bottom (better `top` replacement) cargo install bottom # Install from a git repo cargo install --git https://github.com/you/your-tool.git ``` > [!info] Cargo vs winget for Rust tools > Many popular Rust CLIs (ripgrep, bat, fd, eza, zoxide) are available via both `winget` and `cargo install`. winget gives you precompiled binaries with auto-update through the Microsoft Store ecosystem; `cargo install` compiles from source but gives you the absolute latest version optimized for your CPU (including the ARM64 NEON code paths where the crate enables them). For daily-use tooling, prefer winget; use `cargo install` when winget doesn't carry the tool or you want a specific version. --- ## direnv — auto-activate this project's environment direnv can set dev env vars and put built binaries on `PATH` when you `cd` in (see [[General_Development_Windows_Native_Setup]] for the PowerShell hook). Rust doesn't need direnv to choose a toolchain — that's `rust-toolchain.toml`'s job. ```bash # from the project root cat > .envrc <<'EOF' export RUST_BACKTRACE=1 export RUST_LOG=debug # call target\debug\calc.exe as just `calc` after `cargo build` PATH_add target/debug EOF direnv allow ``` > [!warning] `.envrc` is bash on Windows > direnv runs `.envrc` through bash and applies the result to PowerShell, so write bash syntax (forward slashes are fine) and keep Git for Windows on `PATH`. To pin a toolchain, add a `rust-toolchain.toml` (`[toolchain]` with `channel = "stable"`), which rustup reads automatically — not direnv. --- ## Troubleshooting > [!warning] `rustc: The term 'rustc' is not recognized` after installing > rustup updates PATH in the registry, but existing PowerShell windows cache the old PATH. Open a new Windows Terminal tab. If `rustc` still isn't found, confirm `%USERPROFILE%\.cargo\bin` is in your user PATH: `[System.Environment]::GetEnvironmentVariable('Path','User') -split ';'` should include it. > [!warning] `cargo build` error: "linker `link.exe` not found" > The MSVC Build Tools aren't installed (or aren't on the discoverable path). Re-run the install from [Prerequisite: a C linker](#prerequisite-a-c-linker), then close all PowerShell windows and open a fresh one. Verify with `where.exe link.exe` inside a "Developer PowerShell for VS 2026" tab. > [!warning] `cargo build` error: "Microsoft Visual C++ 14.0 or greater is required" > Same root cause as the previous error — you have rustc but no MSVC. Install the VS 2026 Build Tools with the C++ workload. (The "14.0 or greater" wording in the error message is historical; any modern VS Build Tools install satisfies it.) > [!warning] iced build fails with "failed to find any matching adapter" at runtime > wgpu couldn't initialize a DirectX 12 device. Update your GPU driver — for Surface Pro ARM64, run Windows Update (it ships Qualcomm Adreno drivers); for Intel/AMD/NVIDIA on x64, install the latest driver from the vendor. Inside a VM, enable 3D acceleration in the hypervisor settings. > [!warning] Very slow first build of a GUI project > iced (and egui, and Slint) pull in substantial graphics dependencies. The first `cargo build` for a GUI project typically takes 3–8 minutes; subsequent builds are incremental. This is normal. > [!warning] iced window doesn't open inside a VM > iced needs working DirectX 12 (or fallback Vulkan/GL). Enable 3D acceleration in your hypervisor. The CLI and the state-machine tests run fine without a display, so `cargo test` will still pass headlessly. > [!warning] `rust-analyzer` seems unresponsive in VS Code > Run `cargo check` in the integrated terminal first — rust-analyzer uses cargo under the hood, and if cargo can't find the MSVC linker, rust-analyzer silently fails. Check the "Rust Analyzer Language Server" output panel for errors. > [!warning] `cargo install` fails with "could not compile `windows-sys`" > The crate depends on the Windows SDK headers. Confirm the SDK was installed with the VS Build Tools (`--add Microsoft.VisualStudio.Component.Windows11SDK.26100` in the winget command). Reinstall via `Programs and Features → Visual Studio Build Tools 2026 → Modify → Individual Components → Windows 11 SDK`. > [!warning] Mixed GNU/MSVC toolchains causing strange link errors > Run `rustup show` and confirm the active toolchain ends in `-msvc`. If it's `-gnu`, switch with `rustup default stable-msvc`. Mixing toolchains in one project is unsupported. > [!warning] Windows Defender slows builds dramatically > Real-time scanning checks every `.rlib` and `.o` that cargo writes. Add an exclusion for `%USERPROFILE%\.cargo\` and the per-project `target\` directories: Settings → Privacy & security → Windows Security → Virus & threat protection → Manage settings → Exclusions. This commonly cuts cold build times by 30–50%. --- ## Summary — the one-shot Rust addition > [!warning] This is a checklist, not a script > Copy and paste one block at a time. The MSVC Build Tools step is **multi-gigabyte and takes 10–20 minutes**, and the rustup installer is **interactive** — read its prompts and respond before continuing. The `cargo install` commands compile from source (1–3 minutes each). After running these, **append the Rust settings block** above to `%APPDATA%\Code\User\settings.json`. ```powershell # 1. C linker — Visual Studio 2026 Build Tools with C++ workload + Win11 SDK # (Skip if you already did this for the C guide.) # Run from an elevated PowerShell. winget install -e --id Microsoft.VisualStudio.2026.BuildTools ` --override "--passive --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --includeRecommended" # 2. Rust toolchain (interactive installer — accept defaults) winget install -e --id Rustlang.Rustup # Open a NEW PowerShell tab here so %USERPROFILE%\.cargo\bin is on PATH. # 3. Confirm the right host triple rustup show # Default host should be aarch64-pc-windows-msvc (ARM64) or # x86_64-pc-windows-msvc (x64). If it's *-gnu, fix: # rustup default stable-msvc # 4. Global Cargo tooling cargo install --locked cargo-watch cargo-audit cargo-nextest cargo-edit cargo-outdated # 5. VS Code extensions code --install-extension rust-lang.rust-analyzer code --install-extension vadimcn.vscode-lldb code --install-extension serayuzgur.crates # 6. Paste the Rust block into %APPDATA%\Code\User\settings.json code $env:APPDATA\Code\User\settings.json # 7. Verify rustc --version cargo --version rustup --version ``` About 20–25 minutes of install time on top of the General setup if you don't already have the VS Build Tools (most of which is the Build Tools download). If the C guide already ran, drop that to about 5 minutes. --- ## Related notes **The Rust guide across platforms:** - [[Rust_Development_Mac_Tahoe_Setup]] — the macOS counterpart - [[Rust_Development_Ubuntu_Setup]] — the Linux counterpart **Same Windows native setup, other languages:** - [[General_Development_Windows_Native_Setup]] — the foundation this guide builds on - [[C_Development_Windows_Native_Setup]] — shares the VS Build Tools install with this guide - [[Python_Development_Windows_Native_Setup]] - [[Ruby_Development_Windows_Native_Setup]] - [[Go_Development_Windows_Native_Setup]] **Topic references:** - [Rust on Windows — official setup notes](https://rust-lang.github.io/rustup/installation/windows.html) - [Cargo Cheat Sheet](https://doc.rust-lang.org/cargo/) - [iced Architecture Notes](https://book.iced.rs/architecture.html) - [wgpu — DirectX 12 backend](https://github.com/gfx-rs/wgpu) - [Visual Studio Build Tools — workloads and components](https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools)