← all lessons
Foundations · #1 of 13

Installing Rust and Running Your First Program

Cargo — the one tool that handles every part of a Rust project

Most languages hand you a compiler and wish you luck. You’re left to find a build system, a package manager, a test runner, and a way to glue them together — a weekend lost before you write a line of real code.

Rust hands you one command instead, and it already knows how to do all of that.

Before you can do anything else with Rust, you need a working installation and a way to turn your source code — the text you wrote — into a program the computer can actually run. In Rust, one tool, Cargo, handles this for every project, large or small. Learn Cargo first and every later lesson becomes just “what code do I put in this file?” Cargo handles the rest.

What’s actually happening when you “run a program”

Your source code is just text. A .rs file is no different from a .txt file as far as your computer is concerned — until you ask a compiler to read it. The compiler is itself a program: it reads your text, checks that it makes sense (this is where Rust catches its famous borrow-checker errors), and writes out a new file called a binary or executable. That file is what your computer can run directly.

So “running a Rust program” is really two steps:

  1. Compile: text source code → binary file. The Rust compiler is named rustc. You almost never call it directly; you let Cargo do it.
  2. Execute: run the binary. The operating system loads it into memory and starts following its instructions.

Cargo combines these two steps for you and adds a great deal more on top.

A few words you’ll need

Three tools, one job each

When you install Rust you actually get three programs, stacked on top of one another. Keeping them straight saves a lot of confusion later:

The mental model: rustup manages Rust, cargo manages your project, and rustc is the engine cargo drives so you don’t have to.

Installing Rust

On macOS, Linux, or Windows WSL, the official one-liner is:

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

That installs rustup, which in turn installs rustc and cargo. After installation, restart your terminal and check:

rustc --version
cargo --version

Both should print version numbers. If they do, you’re ready.

Your first project

The code boxes on this page run real Rust in your browser, so you can follow along even before you’ve installed anything. We’ll start with the smallest program there is. Hit Run and watch a real compiler turn this text into output:

The whole program editable · real rustc
Open in Playground ↗ ready

That is the exact contents of src/main.rs in a fresh project. On your own machine you don’t type that file by hand — Cargo scaffolds it. Pick a directory where you keep code, open a terminal there, and run:

cargo new hello

That creates a new directory called hello/ with this shape:

hello/
├── Cargo.toml        ← manifest: name, version, edition, dependencies
├── .gitignore        ← tells git to ignore the build folder
└── src/
    └── main.rs       ← your code starts here

cargo new also runs git init for you, so the project is a git repository from day one. Open src/main.rs and you’ll find the same three lines you just ran above. Now build and run it:

cd hello
cargo run

You’ll see something like:

   Compiling hello v0.1.0 (/path/to/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/hello`
Hello, world!

What just happened:

  1. Cargo read Cargo.toml to learn the project’s name and settings.
  2. It compiled src/main.rs into a binary at target/debug/hello.
  3. It ran that binary.
  4. The binary printed Hello, world!.

You wrote one file; Cargo handled the rest.

What Cargo.toml looks like

Open the manifest:

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

[dependencies]

Plain key-value settings under named sections ([package], [dependencies]). To use someone else’s code you add a line under [dependencies] — but you don’t have to edit the file by hand. There’s a command:

cargo add rand

That looks up the popular rand crate on crates.io, writes a compatible version into Cargo.toml, and downloads the source so the next build can use it. Once a crate is a dependency, you call its functions exactly like the standard library’s. Here rand gives us a random number with no setup of our own:

Using a crate from crates.io editable · real rustc
Open in Playground ↗ ready

(The Playground keeps the most popular crates pre-loaded, which is why rand works here without a cargo add. On your own machine, that one command is the whole installation step.) Run it a few times — the number changes because the program asks the operating system for fresh randomness each run. Version pinning, the lockfile, fetching dependencies-of-dependencies — Cargo handles all of it automatically.

Editions: how Rust changes without breaking your code

You may have noticed edition = "2024" in the manifest. An edition is Rust’s mechanism for evolving the language without breaking the millions of lines of Rust already in the wild. Every few years a new edition (2015, 2018, 2021, 2024) can introduce new keywords or change defaults — things that would otherwise make old code stop compiling.

The trick: each crate declares its own edition in Cargo.toml, and the compiler honors it per crate. A brand-new crate on edition 2024 can depend on an ancient crate still on edition 2015, and both compile in the same build. Nobody is forced to upgrade, and the language is free to improve. You don’t have to think about editions yet — just know that the line in your manifest is what lets a five-year-old library keep working forever.

What an edition can and can't change

An edition can change things that are local to a crate’s own source: which words are reserved as keywords (async and await became keywords in 2018), what a bare extern crate means, how closures capture variables (2021 tightened this), and similar surface-syntax decisions. What an edition can not change is the standard library or the meaning of compiled code — every edition links against the same std and produces the same machine-level result. That’s the whole reason cross-edition dependencies work: the editions differ only in how the text is interpreted, not in what the binary does. The compiler effectively reads each crate in the dialect it declares, then lowers everything to one common representation before generating code. So upgrading an edition is mechanical and low-risk — cargo fix --edition does most of it automatically — and you never have to coordinate the upgrade with anyone else’s crate.

A program is just functions

Real projects are more than one println!. The unit you build with is the function: a named block of code that takes inputs and returns a result. Here greet takes a name and hands back a sentence; main calls it. This is the shape every Rust program grows into.

Your own function editable · real rustc
Open in Playground ↗ ready

Change "world" to your own name and run it again. The signature fn greet(name: &str) -> String reads as “a function named greet that takes a piece of text and returns owned text” — you’ll meet &str and String properly in lesson 4. For now, notice that main stays tiny and the real work lives in a function it calls.

Watch the compiler stop a bug

Here is the experience that makes Rust Rust. The program below tries to store a piece of text in a variable that was promised to hold a whole number. That’s a contradiction, and Rust refuses to build it. Hit Run and read the error before continuing:

A bug the compiler catches before it runs editable · real rustc
Open in Playground ↗ ready

The compiler reports error[E0308]: mismatched types and points at the exact spot: expected u32, found &str. The program never ran and never produced a wrong answer at 3 a.m. in production — the mistake was caught at the door, with a message that names the problem and the line. In many languages this same slip survives to runtime. Rust shifts the failure earlier, to the cheapest possible place to fix it. Every cargo build runs this check first.

The two build modes

When you run cargo build (or cargo run), Cargo defaults to dev mode: compile quickly, run slowly, include extra debugging information. That’s what you want while writing code — fast iteration.

When you’re ready to share the program or measure its real speed, switch to release mode:

cargo build --release

Release mode tells the compiler “spend more time, produce faster code.” The difference is large — a tight numeric loop can be 10 to 100 times faster in release mode, at the cost of slower compilation. Here’s the kind of loop where it shows. It runs fine in the Playground’s debug build; on your machine, --release would make the same work finish far quicker:

The work release mode speeds up editable · real rustc
Open in Playground ↗ ready

The rule of thumb:

The output goes to a different folder too — target/release/ instead of target/debug/ — so you can keep both around.

A faster sanity check

When you just want to know “does my code make sense?” without producing a binary, use:

cargo check

This runs the compiler’s full analysis — all the type-checking and borrow-checking — but stops before generating the binary. It’s much faster than cargo build, and you’ll run it constantly: edit, cargo check, fix the errors, repeat, and only cargo run when it’s clean.

The short history of the box

2009

Bundler ships for Ruby. Its authors learn what a package manager needs to be trusted: a lockfile, semantic versioning, one registry.

2014

Yehuda Katz and Carl Lerche begin Cargo for Rust, carrying Bundler's lessons over. crates.io opens as the central registry.

2015

Rust 1.0 ships with Cargo built in — a rarity. Most languages bolt on a package manager years later; Rust had one on day one.

2018

The first new edition after 2015 arrives, proving the language can evolve (new keywords, new defaults) without breaking existing crates.

2024

The 2024 edition lands — the current default for cargo new — while crates on 2015 still compile untouched, side by side.

Key takeaways

One command stands between you and a running program, and it already knows how to fetch the world’s libraries, build them, run your tests, and stop a type error before it ever reaches a user.

That’s the gift Cargo’s authors carried over from a decade of package-manager scar tissue. With it installed, the rest of this course is simple: edit src/main.rs, run cargo run, watch what happens. The next lesson fills that file with variables and types — the raw material every program is built from.