Setting Up Rust, Cargo, and VS Code

I walk through installing Rust with rustup, what Cargo does, the key fields in Cargo.toml, and wiring up rust-analyzer in VS Code so you can write Rust without fighting the toolchain.

Setting Up Rust, Cargo, and VS Code

The first time I looked up “how to install Rust,” the official docs mentioned three separate things: rustup, rustc, and Cargo. No explanation of why they are different, or which one you actually run day to day. Most tutorials skip straight to writing code, which leaves you with a working environment you do not fully understand.

This post is the setup step. By the end, you will have Rust installed, a compiled project, and VS Code giving you errors and completions before you run a single command.

Quick answer: Run one command from rustup.rs to install the toolchain manager. It installs both the Rust compiler (rustc) and the build tool (cargo) automatically. Create a project with cargo new, then install the rust-lang.rust-analyzer extension in VS Code.

Series: Part 2 of 12.

On this page

What rustup actually installs

rustup is a toolchain manager, not the compiler itself. Think of it the way you think about nvm for Node.js — it installs, updates, and switches between Rust toolchain versions without you manually managing files.

When you run the install command, rustup puts three things on your machine:

  • rustc — the Rust compiler. You will rarely call this directly.
  • cargo — the build tool and package manager. This is what you run constantly.
  • std — the standard library for your target platform.

The reason these are separate tools is that Rust supports many compilation targets (x86, ARM, WebAssembly, embedded microcontrollers) and multiple release channels (stable, beta, nightly). rustup manages all of that complexity so you do not have to think about it.

In practice, you use rustup once — to install and update — and cargo constantly. rustc is mostly invisible; it does the compiling, but cargo invokes it on your behalf.

If you come from TypeScript, cargo fills the role that tsc, npm, and your test runner fill together, except it is one official tool. The TypeScript development experience involves assembling several tools from different sources; Rust ships them in a single package maintained by the same team that maintains the language.

Installing Rust on your machine

On macOS or Linux, open a terminal and run:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

The installer prompts you to choose an installation type. Option 1 (the default) is correct for most setups. It installs the stable toolchain and adds cargo’s bin directory to your PATH.

On Windows, download and run rustup-init.exe from rustup.rs. You may also need the Visual Studio C++ Build Tools — the installer will prompt you if something is missing.

After installation finishes, open a new terminal window and verify both tools are available:

rustc --version
cargo --version

You should see something like this:

rustc 1.87.0 (17067e9ac 2025-05-09)
cargo 1.87.0 (99624be96 2025-05-06)

The exact versions will differ as Rust releases on a six-week cycle, but both lines should show the same version number. If you see command not found, the new terminal trick often fixes it — the PATH change does not apply to terminals that were already open.

To update Rust at any point in the future:

rustup update

That single command updates the compiler, cargo, and the standard library together.

Creating a project with Cargo

With Rust installed, cargo new creates every project you will work on:

cargo new word_count
cd word_count

Cargo produces this directory structure:

word_count/
├── Cargo.toml
└── src/
    └── main.rs

Cargo also initialises a git repository automatically. If you already have git configured, the repository is ready to commit from the moment the project is created.

The src/main.rs file contains a single main function. Replace the default contents with something real:

fn main() {
    let text = "Rust setup is complete";
    println!("Word count: {}", count_words(text));
}

fn count_words(text: &str) -> usize {
    text.split_whitespace().count()
}

This program splits a string on whitespace and counts the parts. It is small, but it compiles, runs, and produces output you can verify — which is exactly what you want when confirming the toolchain works.

Check this before moving on

  • Cargo.toml and src/main.rs exist inside the new project folder
  • The project directory contains a .git folder (git was initialised)
  • You can open src/main.rs and modify it without errors

What is inside Cargo.toml

The Cargo.toml file is the manifest that Cargo reads to understand your project. A freshly created project looks like this:

[package]
name = "word_count"
version = "0.1.0"
edition = "2024"

[dependencies]

name is the package identifier. It becomes the filename of your compiled binary.

version follows Semantic Versioning. Starting at 0.1.0 signals that the API is not stable yet. Move to 1.0.0 when it is.

edition tells the compiler which Rust edition rules to apply. The current default for new projects is "2024". Editions are backward-compatible, meaning older code continues to compile unchanged when you upgrade. The edition field exists because Rust can introduce breaking changes to the language without breaking existing packages — each package opts in to new rules explicitly.

[dependencies] is where external crates go. To add the serde crate for serialisation:

[dependencies]
serde = { version = "1", features = ["derive"] }

Run any cargo command after editing this section and Cargo automatically resolves the dependency graph, downloads the crates it needs, and compiles them. You do not manage a lockfile or a node_modules-style folder by hand — Cargo handles the entire lifecycle.

The Cargo.lock file that appears after your first build is generated automatically. Commit it for applications (it locks exact dependency versions across machines) and add it to .gitignore for libraries (so users resolve fresh dependencies).

Building, running, and testing

Three cargo commands cover the bulk of day-to-day work:

cargo run        # compile and run the binary
cargo build      # compile without running
cargo test       # compile and run all tests

cargo run is the one you reach for most often while developing. It recompiles only what changed since the last run, then executes the result.

cargo build writes the output to target/debug/. The debug build compiles quickly but skips most optimisations, which means the binary runs slower. When you want a real performance measurement:

cargo build --release

The release binary goes to target/release/. Compilation takes longer, but the resulting binary is genuinely fast. Never benchmark debug builds — the numbers will mislead you significantly.

Adding tests is straightforward. Put a test module at the bottom of your source file:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn three_words() {
        assert_eq!(count_words("one two three"), 3);
    }

    #[test]
    fn empty_string() {
        assert_eq!(count_words(""), 0);
    }
}

Run cargo test and Cargo finds every function annotated with #[test] across the entire project and runs them all. The #[cfg(test)] attribute tells the compiler to include this module only when running tests, not in the final binary.

Setting up VS Code with rust-analyzer

Open VS Code, go to Extensions (Ctrl+Shift+X on Windows/Linux, Cmd+Shift+X on macOS), and search for rust-analyzer. Install the extension published by The Rust Programming Language (extension ID: rust-lang.rust-analyzer).

After installing, open your project folder — not a single file inside it, but the folder that contains Cargo.toml. rust-analyzer starts indexing the project in the background. You will see a progress indicator in the status bar. Once it finishes, you get:

  • Inlay hints — the inferred type of every variable, shown inline in grey text
  • Error squiggles — compiler errors highlighted as you type, before you save or run anything
  • Code completion — context-aware suggestions with automatic import insertion
  • Go to definition — jump to where any function or type is defined, including standard library types

The type inlay hints are where rust-analyzer pays for itself immediately. Rust’s type inference means you often do not write types explicitly, but seeing them inline helps you understand what the compiler knows and where it disagrees with your intention.

All rust-analyzer settings live under the rust-analyzer.* namespace in VS Code settings. The defaults work well for most projects — you rarely need to change anything to get a productive environment.

When setup goes wrong

A handful of failures come up repeatedly.

cargo not found after installation. The installer adds cargo’s bin directory to your PATH, but the change only takes effect in new shell sessions. Close and reopen your terminal. If you need it immediately in the current session, run:

source ~/.cargo/env

Linker error on Linux: error: linker 'cc' not found. Rust needs a C linker on Linux. On Debian and Ubuntu:

sudo apt install build-essential

On Fedora or RHEL:

sudo dnf install gcc

On Windows: LINK : fatal error LNK1181. This means the Visual Studio C++ Build Tools are missing. Run the Visual Studio Build Tools installer and select “Desktop development with C++”. Alternatively, add the MinGW toolchain:

rustup target add x86_64-pc-windows-gnu

Then install MinGW-w64 and add it to your PATH.

rust-analyzer shows no hints in VS Code. The most common cause is opening a single .rs file rather than the project folder. rust-analyzer analyses the project, not individual files. Use File → Open Folder and select the directory that contains Cargo.toml.

cargo test output is empty. By default, cargo captures stdout during tests and only shows it when a test fails. To see output from passing tests:

cargo test -- --nocapture

The toolchain is no longer in your way

The three-tool confusion from the beginning resolves cleanly: rustup manages toolchain versions, cargo manages projects, and rustc compiles code. In practice, you run rustup rarely (install once, update occasionally) and cargo constantly.

Your project now compiles, the tests pass, and VS Code shows type information and errors as you type. That baseline removes the toolchain as an obstacle. The language itself — ownership, borrowing, lifetimes — is where the interesting work begins, and that is what the next parts of this series cover.

Sources