Rust Get Started (Hello World)
Rust is a systems programming language that gives you low-level control over memory and performance, similar to C or C++, while using a compiler that catches entire categories of bugs (crashes, data races, memory corruption) before your program ever runs. It has become one of the most loved languages in industry surveys because it lets you write fast, reliable software without needing a garbage collector. This lesson gets Rust installed on your machine, explains the tools you will use every day, and walks through your first program, Hello, world!, in complete detail.
Overview: How the Rust Toolchain Works
Rust is a compiled language, not an interpreted one. When you write Python or JavaScript, an interpreter reads your source code and executes it line by line each time you run it. Rust works differently: a compiler called rustc reads your entire .rs source file, checks it for correctness (syntax, types, and, uniquely to Rust, ownership and borrowing rules you will meet in later lessons), and translates it into a native machine-code binary. That binary is a standalone executable — once it exists, you can run it directly, and it no longer needs Rust installed to execute. This is why Rust programs tend to start instantly and run close to the speed of C.
You will normally interact with three pieces of tooling:
- rustup — the official installer and version manager. It installs
rustc,cargo, and standard documentation, and lets you switch between stable, beta, and nightly Rust or update everything with one command. - rustc — the compiler itself. You can invoke it directly on a single file, which is useful for tiny experiments, but it does not manage dependencies or multi-file projects for you.
- cargo — Rust’s build tool and package manager, and the tool almost every real Rust project is built and run through. Cargo creates a standard project layout, downloads and compiles dependencies (called crates), runs your tests, and builds optimized release binaries.
To install Rust, visit rustup.rs and follow the platform-specific instructions (on Linux/macOS this is typically a single shell command that downloads and runs the rustup installer; on Windows it is a downloadable installer). Once installed, verify it from a terminal:
rustc --version
cargo --version
Both commands should print a version number, such as rustc 1.79.0 (stable). This lesson targets stable Rust on the 2021 edition, which is the default for new projects and the version you should use unless you have a specific reason not to.
A Cargo-managed project has a standard shape: a Cargo.toml file at the root describing the package (name, version, dependencies, and the language edition), and a src/ directory holding your source files, with src/main.rs as the entry point for an executable. You almost never need to memorize this layout — cargo new generates it for you.
Syntax
Every Rust executable needs exactly one function named main, which is where the program begins running. The simplest possible Rust program looks like this:
fn main() {
println!("Hello, world!");
}
| Part | Meaning |
|---|---|
fn |
Keyword that declares a function. |
main |
The function name. main is special: it is the entry point the compiled binary runs first. |
() |
The function’s parameter list — empty here, since main takes no arguments in this example. |
{ ... } |
The function body, delimited by curly braces. |
println! |
A macro (note the !) that prints text to standard output, followed by a newline. |
"Hello, world!" |
A string literal, of type &str (a borrowed string slice). |
; |
Statement terminator. Almost every statement in Rust ends with a semicolon. |
println! is a macro rather than an ordinary function — that is what the trailing ! signals. Macros in Rust can accept a variable number of arguments and perform compile-time checks that a normal function cannot, which is exactly why println! can validate that your {} placeholders match the arguments you pass, catching mismatches as compiler errors instead of runtime bugs.
Examples
Example 1: Hello, World with rustc directly
For a single throwaway file, you can skip Cargo entirely and invoke the compiler yourself.
fn main() {
println!("Hello, world!");
}
Output:
Hello, world!
Save this as main.rs and compile and run it from the terminal:
rustc main.rs
./main
rustc main.rs produces a native executable named main (or main.exe on Windows) in the same directory. Running that binary executes the compiled machine code, calling println! which writes the text and a trailing newline to standard output.
Example 2: The same program, the Cargo way
Real projects use Cargo instead of calling rustc by hand. Create a new project:
cargo new hello_world
cd hello_world
cargo run
This generates a Cargo.toml like the one below and a src/main.rs already containing a Hello World program.
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
Now edit src/main.rs to introduce a variable and formatted output:
fn main() {
let name = "Rustacean";
let year = 2026;
println!("Hello, {}! Welcome to Rust in {}.", name, year);
}
Output:
Hello, Rustacean! Welcome to Rust in 2026.
let name = "Rustacean"; binds an immutable variable of type &str. Each {} in the format string is a placeholder, filled in order by the arguments that follow — name first, then year. cargo run compiles the project (into target/debug/) and immediately executes the resulting binary in one step, which is why it is the command you will use constantly during development.
Example 3: A more realistic version with a function
Most real programs split work into functions rather than writing everything inline in main. Here, a helper function builds a greeting string that main then prints.
fn greet(name: &str, unread_messages: u32) -> String {
format!("Hello, {}! You have {} unread messages.", name, unread_messages)
}
fn main() {
let user = "Ava";
let messages: u32 = 3;
let greeting = greet(user, messages);
println!("{}", greeting);
println!("Have a productive day!");
}
Output:
Hello, Ava! You have 3 unread messages.
Have a productive day!
greet takes a &str (a borrowed view of string data, so it does not need to own or copy the text just to read it) and a u32, and returns an owned String built with the format! macro — format! works exactly like println! except it returns the formatted text instead of printing it. Back in main, that String is stored in greeting and printed with a second println! call.
How It Works Step by Step
When you run cargo run (or rustc directly), several stages happen before you see any output:
- Parsing: The compiler reads your
.rsfile and builds a syntax tree, checking that the code is grammatically valid Rust. - Macro expansion: Macro calls like
println!("Hello, {}", name)are expanded into the lower-level code that actually formats the string and writes it to standard output. - Type checking and borrow checking: The compiler verifies every expression’s type is consistent, and — for programs with references, which you will use heavily in later lessons — verifies borrowing rules are respected. This is also where ownership violations would be caught, though this simple program has none.
- Code generation: Once the program passes all checks, the compiler generates native machine code for your target platform (via LLVM under the hood).
- Linking: The generated code is linked with the Rust standard library and any dependencies into a single executable file.
- Execution: The operating system loads and runs that executable.
mainruns first; each statement inside it executes top to bottom, so the twoprintln!calls in Example 3 print in the order they appear.
With cargo run, Cargo performs the first five steps for you (skipping recompilation if nothing changed since the last build) and then launches the resulting binary automatically.
Common Mistakes
1. Forgetting a semicolon
Rust statements need a trailing semicolon. Leaving one off produces a compile error rather than silently working:
fn main() {
let x = 5
println!("x is {}", x);
}
This fails with an error similar to expected `;`, found `println`, because the compiler expected the statement to end before the next line began. The fix is simply adding the missing semicolon:
let x = 5;
println!("x is {}", x);
2. Using single quotes for a string
Rust uses single quotes for a single char and double quotes for a string. Writing multi-character text in single quotes is a common mistake for people coming from languages where quote style does not matter:
fn main() {
let message = 'Hello, world!';
println!("{}", message);
}
This fails to compile with an error like character literal may only contain one codepoint, because '...' in Rust always means a single Unicode character, never a string. The fix is to use double quotes:
let message = "Hello, world!";
println!("{}", message);
3. Mismatched println! placeholders
Because println! checks its format string at compile time, supplying the wrong number of arguments is caught immediately rather than producing garbled output at runtime:
fn main() {
let count = 5;
println!("You have {} new messages and {} alerts.", count);
}
This fails to compile because the format string has two {} placeholders but only one argument was provided. The fix is to supply a value for every placeholder:
let count = 5;
let alerts = 2;
println!("You have {} new messages and {} alerts.", count, alerts);
Best Practices
- Use
cargo newfor anything beyond a single throwaway file — it sets up the standard layout that every other Rust tool expects. - Run
cargo checkwhile developing; it type-checks and borrow-checks your code without producing a binary, which is much faster than a fullcargo build. - Use
cargo runduring development andcargo build --release(orcargo run --release) when you need an optimized binary for benchmarking or distribution. - Run
cargo fmtregularly so your code follows the community-standard formatting everyone else’s Rust code uses. - Keep
rustupitself up to date withrustup updateso you stay on the latest stable compiler and its improved error messages. - Read compiler errors fully —
rustc‘s error messages usually explain exactly what is wrong and often suggest the fix directly.
Practice Exercises
- Install Rust via
rustupif you have not already, then confirm the install by printing bothrustc --versionandcargo --versionin your terminal. - Use
cargo newto create a project calledgreeter, then editsrc/main.rsso it prints your name and your favorite programming language on two separate lines using twoprintln!calls. - Write a program with two variables, a
&strname and a numeric age, and use a singleprintln!call with two{}placeholders to print"with real values substituted in.is years old."
Summary
- Rust is compiled ahead of time by
rustcinto a native executable — there is no interpreter running your source code line by line. rustupinstalls and manages your Rust toolchain;rustcis the compiler;cargois the build tool and package manager used for almost all real projects.- Every executable needs a
fn main()entry point, which is where execution begins. println!andformat!are macros (identifiable by the trailing!) that check their{}placeholders against their arguments at compile time.cargo newscaffolds a standard project with aCargo.tomlmanifest and asrc/main.rsentry file;cargo runbuilds and runs it in one step.- Common beginner mistakes — missing semicolons, single-quoting a string, and mismatched format placeholders — are all caught by the compiler before your program ever runs.
