Getting Started with Rust
Every Rust program you write in this track starts the same way: a project
created by Cargo, a main function, and a line of output. This lesson sets up
that loop. You will install the toolchain, create and run a project, and learn
how println! formats values, which you will use in every lesson after this
one.
Installing Rust
Rust is installed with rustup, a small tool that downloads the compiler and
keeps it up to date. On macOS and Linux, run:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shOn Windows, download and run rustup-init.exe from
rustup.rs. Once it finishes, open a new terminal and check
that the tools are on your PATH:
rustc --version
cargo --versionrustup installs three things you will use constantly:
rustc- the compiler. You rarely call it directly.cargo- the build tool and package manager. This is what you run day to day.rustupitself - runrustup updatenow and then to get the latest stable release.
Creating a project with Cargo
Cargo creates a new project with one command:
cargo new hello
cd helloThat gives you this layout:
hello
├── Cargo.toml
└── src
└── main.rsCargo.toml is the project's manifest. It holds the package name, its version,
the Rust edition it is written against, and later the libraries (crates) it
depends on:
[package]
name = "hello"
version = "0.1.0"
edition = "2024"
[dependencies]src/main.rs is the program itself. Cargo fills it with a working example:
fn main() {
println!("Hello, world!");
}cargo new also runs git init for you, with a .gitignore that excludes the
target/ directory where build output goes.
Running your program
cargo run compiles the project and runs the result:
cargo run Compiling hello v0.1.0 (/home/you/hello)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/hello`
Hello, world!The first three lines come from Cargo; the last is your program. There are two other commands worth knowing from day one:
cargo buildcompiles without running. The binary lands intarget/debug/hello.cargo checktype-checks the code without producing a binary. It is much faster than a full build, so it is what you run while fixing compiler errors.
When you ship a program, cargo build --release turns on optimisations and
puts the binary in target/release/ instead.
Anatomy of main
Look again at the generated file:
fn main() {
println!("Hello, world!");
}fndeclares a function.mainis special: it is where every Rust binary starts.- The body goes between curly braces.
println!("...");is a statement, and statements end with a semicolon.
The ! after println matters. It means println! is a macro, not a
function. A macro is code that writes code: before your program is compiled,
the macro call is expanded into ordinary Rust. println! is a macro because it
needs things a normal function cannot do, such as accepting any number of
arguments and checking your format string against them at compile time. You
will write your own macros near the end of this track; for now, just remember
that a name ending in ! is a macro call.
Format strings
The first argument to println! is a format string. Each {} in it is a
placeholder that is filled with the next argument:
fn main() {
let name = "Ferris";
let legs = 10;
println!("Hello, {}!", name);
println!("{} has {} legs", name, legs);
println!("{name} has {legs} legs");
println!("{0} likes {1}, and {1} likes {0}", "Ferris", "Rust");
}Hello, Ferris!
Ferris has 10 legs
Ferris has 10 legs
Ferris likes Rust, and Rust likes FerrisLine by line:
{}placeholders are filled in order.{name}puts a variable in the string directly. This is usually the most readable form, and it works for plain variable names (not expressions likelegs + 1).{0}and{1}refer to arguments by position, so you can reuse one.
Don't worry about let yet; the next lesson covers variables.
print!, eprintln! and escaping braces
println! has a few siblings:
fn main() {
print!("Loading");
print!(".");
print!(".");
println!(".");
println!("Done");
eprintln!("this line goes to stderr");
println!("{{braces}} are escaped by doubling them");
}Loading...
Done
this line goes to stderr
{braces} are escaped by doubling themprint!writes without a newline at the end.eprintln!(andeprint!) write to standard error instead of standard output. Use them for error messages and diagnostics, so they don't get mixed into output that another program might read.- To print a literal brace, double it:
{{and}}.
Debug formatting with {:?}
A plain {} uses the Display trait, which is meant for output aimed at
users. Many types, like arrays and tuples, don't implement it, because there is
no single obvious way to show them to a user. They do implement Debug, which
is output aimed at you, the programmer. Ask for it with {:?}:
fn main() {
let numbers = [1, 2, 3];
let pair = ("Ferris", 10);
println!("{:?}", numbers);
println!("{pair:?}");
println!("{pair:#?}");
}[1, 2, 3]
("Ferris", 10)
(
"Ferris",
10,
){:#?} is the "pretty" form that spreads the value over several lines, which
helps once values get large. When you define your own types later, you will
make them printable with #[derive(Debug)].
Width, alignment and precision
Anything after the : in a placeholder controls how the value is laid out:
fn main() {
let pi = 3.14159265;
println!("{:.2}", pi);
println!("[{:>6}]", "rust");
println!("[{:<6}]", "rust");
println!("[{:^6}]", "rust");
println!("[{:05}]", 42);
}3.14
[ rust]
[rust ]
[ rust ]
[00042].2 sets two decimal places, >, < and ^ align right, left and centre
within the given width, and a leading 0 pads numbers with zeros. You won't
need these often, but they are handy for tables and reports. The full syntax is
in the std::fmt docs.
Common mistakes
Forgetting the !
Without the !, Rust looks for a function called println and doesn't find
one:
fn main() {
println("Hello, Ferris!");
}The compiler tells you exactly what to add. Rust's error messages are usually
this helpful, so read them all the way to the help: line before changing
anything.
Placeholders and arguments don't match
Because println! is a macro, it counts your placeholders at compile time. One
too few arguments is an error, not a runtime surprise:
fn main() {
let name = "Ferris";
println!("{} is {} years old", name);
}Passing an extra argument that no placeholder uses is an error too.
Using {} for a type without Display
fn main() {
let numbers = [1, 2, 3];
println!("{}", numbers);
}The note: line has the fix: switch to {:?}.
A missing semicolon
Statements inside a function end with ;. Leave one off between two
statements and the compiler points at the exact spot:
error: expected `;`, found `println`
--> src/main.rs:2:22
|
2 | println!("first")
| ^ help: add `;` here
3 | println!("second");
| ------- unexpected tokenSummary
- Install Rust with
rustup; usecargo new,cargo run,cargo buildandcargo checkto work with projects. - Every binary starts at
fn main(). println!is a macro (note the!). It prints a line;print!skips the newline;eprintln!writes to stderr.{}usesDisplay,{:?}usesDebug, and{:#?}pretty-prints.- Placeholders can be positional (
{}), indexed ({0}) or named ({name}), and the compiler checks them against the arguments. - Read compiler errors to the end: the
help:andnote:lines usually tell you the fix.
Practise what you read
Challenges that use the ideas from this lesson.