Lesson 4 of 94
Hello World & Printing Output
Every example in this course prints its results, so this lesson comes first. You will learn Rust's formatting macros.
The examples use let to create variables. The next lesson, Variables & Mutability, covers it fully.
| Task | Rust |
|---|---|
| Print a line | println!("{}", x) |
| Print without a newline | print!("{}", x) |
| Print an error line | eprintln!("{}", x) |
Build a String |
format!("{} items", n) |
| Print with 2 decimal places | println!("{:.2}", x) |
1. Hello, world
fn main() {
println!("Hello, world!");
}Hello, world!println! prints a line followed by a newline. The ! means println! is a macro, not a function. Macros can do things functions cannot, such as accepting a variable number of arguments and checking the format string at compile time.
2. Printing several lines
fn main() {
println!("Order received");
println!("Payment captured");
println!("Order shipped");
}Order received
Payment captured
Order shipped3. print! without a newline
print! does not add a newline.
fn main() {
print!("Loading");
print!(".");
print!(".");
print!(".");
println!(" done");
}Loading... done4. Placeholders with {}
Each {} is replaced by the next argument, in order. {} works for every type that can be displayed, so you never pick a format per type.
fn main() {
let name = "Ada";
let age = 36;
println!("{} is {} years old", name, age);
}Ada is 36 years old5. Variables directly inside the braces
You can write a variable's name inside the braces. This is the most common style in modern Rust.
fn main() {
let city = "Lisbon";
let temperature = 23;
println!("It is {temperature}°C in {city}");
}It is 23°C in LisbonOnly plain variable names work inside the braces. Expressions such as {age + 1} do not, so pass them as arguments instead:
fn main() {
let age = 36;
println!("Next year: {}", age + 1);
}Next year: 376. The compiler checks your format string
A mismatch between placeholders and arguments is a compile error.
fn main() {
let first = "Grace";
println!("{} {}", first); // two placeholders, one argument
}error: 2 positional arguments in format string, but there is 1 argument
--> src/main.rs:3:15
|
3 | println!("{} {}", first); // two placeholders, one argument
| ^^ ^^ -----7. Positional arguments
A number inside the braces picks an argument by position, starting at 0. You can reuse an argument.
fn main() {
println!("{0} and {1}, then {1} and {0}", "ping", "pong");
}ping and pong, then pong and ping8. Named arguments
fn main() {
println!(
"{user} bought {qty} x {item}",
user = "maria",
qty = 3,
item = "coffee beans"
);
}maria bought 3 x coffee beans9. Printing { and } literally
Double the braces to print them.
fn main() {
let key = "retries";
let value = 3;
println!("{{ \"{key}\": {value} }}");
}{ "retries": 3 }10. Escape sequences
Rust supports these escapes: \n for a newline, \t for a tab, \" for a quote, and \\ for a backslash.
fn main() {
println!("Name:\tAda\nRole:\tEngineer");
println!("Path: C:\\Users\\ada");
println!("She said \"hi\"");
}Name: Ada
Role: Engineer
Path: C:\Users\ada
She said "hi"11. Raw strings for paths and regular expressions
A raw string r"..." ignores escapes. Add # marks when the text itself contains quotes: r#"..."#.
fn main() {
println!(r"C:\Users\ada\notes.txt");
println!("{}", r"^\d{3}-\d{4}$");
println!("{}", r#"{"id": 7, "status": "active"}"#);
}C:\Users\ada\notes.txt
^\d{3}-\d{4}$
{"id": 7, "status": "active"}Notice the last two lines pass the raw string as an argument to "{}". The first argument of println! is always a format string, so braces inside it are placeholders even in a raw string. When text contains { or }, print it through {} instead.
12. Multi-line strings
A normal string literal can span lines. End a line with \ to join it with the next line and skip the leading spaces.
fn main() {
println!("Dear customer,
Your order has shipped.");
println!("This is one long line \
written across two lines in the source.");
}Dear customer,
Your order has shipped.
This is one long line written across two lines in the source.13. Width and alignment
{:10} pads the value to 10 characters. < aligns left, > aligns right, and ^ centers. Text aligns left by default and numbers align right.
fn main() {
println!("[{:<10}]", "left");
println!("[{:>10}]", "right");
println!("[{:^10}]", "center");
println!("[{:10}]", 42);
}[left ]
[ right]
[ center ]
[ 42]14. Custom fill characters
Put any character before the alignment sign to use it for padding.
fn main() {
println!("{:=^30}", " REPORT ");
println!("{:.<20}{:.>10}", "Subtotal", "42.00");
println!("{:*>8}", 99);
}=========== REPORT ===========
Subtotal.................42.00
******9915. Decimal places with precision
{:.2} rounds a floating-point number to 2 decimal places.
fn main() {
let price = 19.989;
let pi = 3.14159265;
println!("Price: {:.2}", price);
println!("Pi: {:.4}", pi);
println!("Whole: {:.0}", 2.5);
println!("Whole: {:.0}", 3.5);
}Price: 19.99
Pi: 3.1416
Whole: 2
Whole: 4The last two lines show that rounding goes to the nearest even number when a value is exactly halfway (2.5 becomes 2, 3.5 becomes 4). This is called banker's rounding.
16. Width and precision together
fn main() {
let amount = 7.5;
println!("[{:8.2}]", amount);
println!("[{:<8.2}]", amount);
println!("[{:08.2}]", amount);
}[ 7.50]
[7.50 ]
[00007.50]17. Zero padding and signs
fn main() {
let invoice_number = 42;
let change = 5;
let loss = -3;
println!("INV-{:06}", invoice_number);
println!("{:+}", change);
println!("{:+}", loss);
}INV-000042
+5
-318. Hexadecimal, binary, and octal
Add # to include the 0x, 0b, or 0o prefix.
fn main() {
let value = 255;
println!("hex: {:x} / {:X} / {:#x}", value, value, value);
println!("binary: {:b} / {:#b}", 5, 5);
println!("octal: {:o} / {:#o}", 8, 8);
println!("padded binary: {:08b}", 5);
println!("colour: #{:02X}{:02X}{:02X}", 255, 128, 0);
}hex: ff / FF / 0xff
binary: 101 / 0b101
octal: 10 / 0o10
padded binary: 00000101
colour: #FF800019. Scientific notation
fn main() {
let distance_to_sun_km = 149600000.0;
let electron_mass_kg = 0.000000000000000000000000000000910938;
println!("{:e}", distance_to_sun_km);
println!("{:e}", electron_mass_kg);
}1.496e8
9.10938e-3120. Width taken from a variable
Write name$ to read the width (or precision) from a variable. This lets you build tables whose column width is computed at runtime.
fn main() {
let width = 12;
let decimals = 3;
println!("[{:>width$}]", "right");
println!("[{:.decimals$}]", 2.0 / 3.0);
println!("[{:>width$.decimals$}]", 1.0 / 7.0);
}[ right]
[0.667]
[ 0.143]21. Building a string with format!
format! works like println! but returns the text as a String instead of printing it. It is Rust's String.format. String is covered properly in a later lesson.
fn main() {
let user = "ana";
let id = 7;
let key = format!("user:{id}:{user}");
let message = format!("Cache key is {key} ({} characters)", key.len());
println!("{message}");
}Cache key is user:7:ana (10 characters)22. Writing errors with eprintln!
eprintln! writes to standard error, like System.err.println. Use it for diagnostics so they do not mix with a program's real output when that output is redirected to a file.
fn main() {
println!("result=42");
eprintln!("warning: config file not found, using defaults");
}result=42The output block shows only standard output. The warning goes to standard error, which your terminal also shows.
23. Debug formatting with {:?}
{} uses the Display format, meant for end users. {:?} uses the Debug format, meant for developers. For text, Debug adds quotes and shows escapes, which makes stray spaces and newlines visible.
fn main() {
let input = " admin\n";
println!("Display: [{}]", input);
println!("Debug: [{:?}]", input);
}Display: [ admin
]
Debug: [" admin\n"]Some types, such as tuples and arrays, can only be printed with {:?}. You will see this in Tuples & Arrays, and the reason in Deriving Standard Traits.
24. Quick debugging with dbg!
dbg! prints the file, line, the expression's source code, and its value to standard error, then returns the value. You can wrap it around any expression without changing what your program does.
fn main() {
let base = 120;
let with_tax = dbg!(base * 2) + 1;
println!("{with_tax}");
}241On standard error you would see [src/main.rs:3:20] base * 2 = 240. Remove dbg! calls before committing, as you would remove temporary System.out.println debugging.
25. Realistic example: a shopping receipt
This combines fill characters, alignment, width, and precision.
fn main() {
let store = "CORNER GROCERY";
println!("{:=^34}", "");
println!("{:^34}", store);
println!("{:=^34}", "");
println!("{:<18}{:>4}{:>12}", "Item", "Qty", "Price");
println!("{:-<34}", "");
println!("{:<18}{:>4}{:>12.2}", "Sourdough bread", 1, 4.5);
println!("{:<18}{:>4}{:>12.2}", "Oat milk", 2, 3.98);
println!("{:<18}{:>4}{:>12.2}", "Espresso beans", 1, 12.0);
println!("{:-<34}", "");
println!("{:<22}{:>12.2}", "TOTAL", 4.5 + 3.98 + 12.0);
}==================================
CORNER GROCERY
==================================
Item Qty Price
----------------------------------
Sourdough bread 1 4.50
Oat milk 2 3.98
Espresso beans 1 12.00
----------------------------------
TOTAL 20.4826. Realistic example: a progress report
fn main() {
let job = "nightly-backup";
let done = 37;
let total = 50;
let percent = done as f64 / total as f64 * 100.0;
println!("[{job}] {done}/{total} files ({percent:.1}%)");
println!("[{job}] {:>5.1}% complete", percent);
}[nightly-backup] 37/50 files (74.0%)
[nightly-backup] 74.0% completedone as f64 converts an integer to a floating-point number. It is covered in Scalar Types.
27. Realistic example: a log line
Production logs often use a fixed layout so they line up and are easy to scan. This example also uses format specs with named variables, such as {level:<5}.
fn main() {
let timestamp = "2026-09-27T10:15:02Z";
let level = "WARN";
let service = "payments";
let latency_ms = 1840;
let request_id = 48879;
println!("{timestamp} {level:<5} [{service:>10}] req={request_id:#06x} slow response: {latency_ms}ms");
let level = "INFO";
let service = "auth";
println!("{timestamp} {level:<5} [{service:>10}] req={:#06x} login ok", 4097);
}2026-09-27T10:15:02Z WARN [ payments] req=0xbeef slow response: 1840ms
2026-09-27T10:15:02Z INFO [ auth] req=0x1001 login okFormat spec cheat sheet
The general shape is {name:fill align sign # 0 width .precision type}. Every part is optional.
| Spec | Meaning | Example | Result |
|---|---|---|---|
{} |
Display | format!("{}", 5) |
5 |
{:?} |
Debug | format!("{:?}", "hi") |
"hi" |
{:#?} |
Pretty Debug (multi-line) | used for structs later | |
{:>8} |
Right-align in 8 characters | format!("{:>8}", 5) |
5 |
{:<8} |
Left-align | format!("{:<8}", 5) |
5 |
{:^8} |
Center | format!("{:^8}", 5) |
5 |
{:*^8} |
Center, fill with * |
format!("{:*^8}", 5) |
***5**** |
{:08.2} |
Zero-pad, 2 decimals | format!("{:08.2}", 3.14159) |
00003.14 |
{:+} |
Always show sign | format!("{:+}", 5) |
+5 |
{:x} {:X} {:#x} |
Hexadecimal | format!("{:#x}", 255) |
0xff |
{:b} {:o} {:e} |
Binary, octal, exponent | format!("{:b}", 5) |
101 |
{:>w$} |
Width from variable w |
Next: Variables & Mutability.
