Guide contents

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

Rust
fn main() {
    println!("Hello, world!");
}
Output
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

Rust
fn main() {
    println!("Order received");
    println!("Payment captured");
    println!("Order shipped");
}
Output
Order received
Payment captured
Order shipped

3. print! without a newline

print! does not add a newline.

Rust
fn main() {
    print!("Loading");
    print!(".");
    print!(".");
    print!(".");
    println!(" done");
}
Output
Loading... done

4. 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.

Rust
fn main() {
    let name = "Ada";
    let age = 36;
    println!("{} is {} years old", name, age);
}
Output
Ada is 36 years old

5. Variables directly inside the braces

You can write a variable's name inside the braces. This is the most common style in modern Rust.

Rust
fn main() {
    let city = "Lisbon";
    let temperature = 23;
    println!("It is {temperature}°C in {city}");
}
Output
It is 23°C in Lisbon

Only plain variable names work inside the braces. Expressions such as {age + 1} do not, so pass them as arguments instead:

Rust
fn main() {
    let age = 36;
    println!("Next year: {}", age + 1);
}
Output
Next year: 37

6. The compiler checks your format string

A mismatch between placeholders and arguments is a compile error.

Rust✗ does not compile
fn main() {
    let first = "Grace";
    println!("{} {}", first); // two placeholders, one argument
}
Output
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.

Rust
fn main() {
    println!("{0} and {1}, then {1} and {0}", "ping", "pong");
}
Output
ping and pong, then pong and ping

8. Named arguments

Rust
fn main() {
    println!(
        "{user} bought {qty} x {item}",
        user = "maria",
        qty = 3,
        item = "coffee beans"
    );
}
Output
maria bought 3 x coffee beans

9. Printing { and } literally

Double the braces to print them.

Rust
fn main() {
    let key = "retries";
    let value = 3;
    println!("{{ \"{key}\": {value} }}");
}
Output
{ "retries": 3 }

10. Escape sequences

Rust supports these escapes: \n for a newline, \t for a tab, \" for a quote, and \\ for a backslash.

Rust
fn main() {
    println!("Name:\tAda\nRole:\tEngineer");
    println!("Path: C:\\Users\\ada");
    println!("She said \"hi\"");
}
Output
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#"..."#.

Rust
fn main() {
    println!(r"C:\Users\ada\notes.txt");
    println!("{}", r"^\d{3}-\d{4}$");
    println!("{}", r#"{"id": 7, "status": "active"}"#);
}
Output
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.

Rust
fn main() {
    println!("Dear customer,
Your order has shipped.");

    println!("This is one long line \
              written across two lines in the source.");
}
Output
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.

Rust
fn main() {
    println!("[{:<10}]", "left");
    println!("[{:>10}]", "right");
    println!("[{:^10}]", "center");
    println!("[{:10}]", 42);
}
Output
[left      ]
[     right]
[  center  ]
[        42]

14. Custom fill characters

Put any character before the alignment sign to use it for padding.

Rust
fn main() {
    println!("{:=^30}", " REPORT ");
    println!("{:.<20}{:.>10}", "Subtotal", "42.00");
    println!("{:*>8}", 99);
}
Output
=========== REPORT ===========
Subtotal.................42.00
******99

15. Decimal places with precision

{:.2} rounds a floating-point number to 2 decimal places.

Rust
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);
}
Output
Price: 19.99
Pi: 3.1416
Whole: 2
Whole: 4

The 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

Rust
fn main() {
    let amount = 7.5;
    println!("[{:8.2}]", amount);
    println!("[{:<8.2}]", amount);
    println!("[{:08.2}]", amount);
}
Output
[    7.50]
[7.50    ]
[00007.50]

17. Zero padding and signs

Rust
fn main() {
    let invoice_number = 42;
    let change = 5;
    let loss = -3;
    println!("INV-{:06}", invoice_number);
    println!("{:+}", change);
    println!("{:+}", loss);
}
Output
INV-000042
+5
-3

18. Hexadecimal, binary, and octal

Add # to include the 0x, 0b, or 0o prefix.

Rust
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);
}
Output
hex: ff / FF / 0xff
binary: 101 / 0b101
octal: 10 / 0o10
padded binary: 00000101
colour: #FF8000

19. Scientific notation

Rust
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);
}
Output
1.496e8
9.10938e-31

20. 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.

Rust
fn main() {
    let width = 12;
    let decimals = 3;
    println!("[{:>width$}]", "right");
    println!("[{:.decimals$}]", 2.0 / 3.0);
    println!("[{:>width$.decimals$}]", 1.0 / 7.0);
}
Output
[       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.

Rust
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}");
}
Output
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.

Rust
fn main() {
    println!("result=42");
    eprintln!("warning: config file not found, using defaults");
}
Output
result=42

The 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.

Rust
fn main() {
    let input = "  admin\n";
    println!("Display: [{}]", input);
    println!("Debug:   [{:?}]", input);
}
Output
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.

Rust
fn main() {
    let base = 120;
    let with_tax = dbg!(base * 2) + 1;
    println!("{with_tax}");
}
Output
241

On 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.

Rust
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);
}
Output
==================================
          CORNER GROCERY
==================================
Item               Qty       Price
----------------------------------
Sourdough bread      1        4.50
Oat milk             2        3.98
Espresso beans       1       12.00
----------------------------------
TOTAL                        20.48

26. Realistic example: a progress report

Rust
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);
}
Output
[nightly-backup] 37/50 files (74.0%)
[nightly-backup]  74.0% complete

done 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}.

Rust
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);
}
Output
2026-09-27T10:15:02Z WARN  [  payments] req=0xbeef slow response: 1840ms
2026-09-27T10:15:02Z INFO  [      auth] req=0x1001 login ok

Format 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.