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 .rs file 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. main runs first; each statement inside it executes top to bottom, so the two println! 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 new for anything beyond a single throwaway file — it sets up the standard layout that every other Rust tool expects.
  • Run cargo check while developing; it type-checks and borrow-checks your code without producing a binary, which is much faster than a full cargo build.
  • Use cargo run during development and cargo build --release (or cargo run --release) when you need an optimized binary for benchmarking or distribution.
  • Run cargo fmt regularly so your code follows the community-standard formatting everyone else’s Rust code uses.
  • Keep rustup itself up to date with rustup update so 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 rustup if you have not already, then confirm the install by printing both rustc --version and cargo --version in your terminal.
  • Use cargo new to create a project called greeter, then edit src/main.rs so it prints your name and your favorite programming language on two separate lines using two println! calls.
  • Write a program with two variables, a &str name and a numeric age, and use a single println! call with two {} placeholders to print " is years old." with real values substituted in.

Summary

  • Rust is compiled ahead of time by rustc into a native executable — there is no interpreter running your source code line by line.
  • rustup installs and manages your Rust toolchain; rustc is the compiler; cargo is 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! and format! are macros (identifiable by the trailing !) that check their {} placeholders against their arguments at compile time.
  • cargo new scaffolds a standard project with a Cargo.toml manifest and a src/main.rs entry file; cargo run builds 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.