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.

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 withcargo new, then install therust-lang.rust-analyzerextension in VS Code.
Series: Part 2 of 12.
On this page
- What rustup actually installs
- Installing Rust on your machine
- Creating a project with Cargo
- What is inside Cargo.toml
- Building, running, and testing
- Setting up VS Code with rust-analyzer
- When setup goes wrong
- The toolchain is no longer in your way
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.tomlandsrc/main.rsexist inside the new project folder - The project directory contains a
.gitfolder (git was initialised) - You can open
src/main.rsand 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.