Skip to content

Output and Formatting

11 min read

Learn how to print output in Rust using the println! macro and its powerful formatting capabilities.


Rust uses macros for printing:

  • println!() - Print with newline
  • print!() - Print without newline
  • Format strings use {} placeholders
  • Rich formatting options built-in

Similar to Python’s f-strings or C’s printf!


// Simple printing
console.log("Hello, world!");
// With variables
const name = "Alice";
const age = 30;
// Template literals
console.log(`Hello, ${name}!`);
console.log(`${name} is ${age} years old`);
// Multiple values
console.log("Name:", name, "Age:", age);
// Formatting numbers
const pi = 3.14159;
console.log(pi.toFixed(2)); // "3.14"
// Objects
const user = { name: "Bob", age: 25 };
console.log(user); // { name: 'Bob', age: 25 }
console.log(JSON.stringify(user)); // {"name":"Bob","age":25}

TypeScript uses template literals and method calls.


// Simple printing
println!("Hello, world!");
// With variables
let name = "Alice";
let age = 30;
// Format strings
println!("Hello, {}!", name);
println!("{} is {} years old", name, age);
// Inline capture (placeholder names a variable in scope)
println!("Hello, {name}!");
// Named arguments (placeholder maps to an explicit value)
println!("Hello, {who}!", who = name);
// Formatting numbers
let pi = 3.14159;
println!("{:.2}", pi); // "3.14"
// Debug formatting
let user = ("Bob", 25);
println!("{:?}", user); // ("Bob", 25)
// Pretty debug
println!("{:#?}", user);

Rust uses macros with powerful format strings.


fn main() {
// Print with newline
println!("Hello!");
println!("World!");
// Print without newline
print!("Hello, ");
print!("World!\n");
// Print to stderr
eprintln!("Error message!");
}

Output:

Hello!
World!
Hello, World!
Error message!
fn main() {
let name = "Alice";
let age = 30;
// Positional
println!("{} is {} years old", name, age);
// Indexed
println!("{0} is {1} years old", name, age);
println!("{1} is the age of {0}", name, age); // Reorder!
// Inline capture (names a variable already in scope)
println!("{name} is {age} years old");
// Named arguments (the placeholder maps to a value you pass)
println!("{who} is {years} years old", who = name, years = age);
// Mixed positional and named
println!("{0} is {years} years old", name, years = age);
}

Compare to TypeScript:

const name = "Alice";
const age = 30;
// Template literals
console.log(`${name} is ${age} years old`);
// Only sequential order possible

Display ({}) - Human-readable output:

let x = 42;
let s = "hello";
let b = true;
println!("{}", x); // 42
println!("{}", s); // hello
println!("{}", b); // true

Debug ({:?}) - Programmer-friendly output:

let tuple = (1, "hello", true);
let vec = vec![1, 2, 3];
// println!("{}", tuple); // Error: tuples don't implement Display
println!("{:?}", tuple); // (1, "hello", true)
println!("{:?}", vec); // [1, 2, 3]

Pretty Debug ({:#?}) - Formatted debug:

let data = vec![
("Alice", 30),
("Bob", 25),
("Charlie", 35),
];
println!("{:#?}", data);

Output:

[
(
"Alice",
30,
),
(
"Bob",
25,
),
(
"Charlie",
35,
),
]
let pi = 3.14159;
// Decimal places
println!("{:.2}", pi); // 3.14
println!("{:.4}", pi); // 3.1416
// Width and alignment
println!("{:10}", 42); // " 42" (right-aligned)
println!("{:<10}", 42); // "42 " (left-aligned)
println!("{:^10}", 42); // " 42 " (centered)
// Zero-padding
println!("{:05}", 42); // 00042
// Sign
println!("{:+}", 42); // +42
println!("{:+}", -42); // -42
// Hexadecimal
println!("{:x}", 255); // ff
println!("{:X}", 255); // FF
println!("{:#x}", 255); // 0xff
// Binary
println!("{:b}", 42); // 101010
println!("{:#b}", 42); // 0b101010
// Octal
println!("{:o}", 42); // 52
println!("{:#o}", 42); // 0o52

Compare to TypeScript:

const pi = 3.14159;
console.log(pi.toFixed(2)); // "3.14"
console.log(pi.toPrecision(4)); // "3.142"
const n = 255;
console.log(n.toString(16)); // "ff"
console.log(n.toString(2)); // "11111111"
let name = "Alice";
// Truncate
println!("{:.5}", name); // Alice (already < 5)
println!("{:.3}", name); // Ali
// Width and fill
println!("{:10}", name); // "Alice "
println!("{:<10}", name); // "Alice " (left)
println!("{:>10}", name); // " Alice" (right)
println!("{:^10}", name); // " Alice " (center)
println!("{:*<10}", name); // "Alice*****" (custom fill)
println!("New line\n");
println!("Tab\there");
println!("Backslash: \\");
println!("Quote: \"");
println!("Single quote: \'");
println!("Unicode: \u{1F44D}"); //

Create formatted strings without printing:

let name = "Alice";
let age = 30;
// Create a String
let s = format!("{} is {} years old", name, age);
println!("{}", s); // Alice is 30 years old
// Useful for building strings
let filename = format!("report_{}.txt", 2024);

Compare to TypeScript:

const name = "Alice";
const age = 30;
const s = `${name} is ${age} years old`;
console.log(s);

Key Differences from TypeScript/JavaScript

Section titled “Key Differences from TypeScript/JavaScript”

TypeScript:

console.log("Hello"); // Function call

Rust:

println!("Hello"); // Macro invocation (note the !)

Why macros? They’re expanded at compile time and can check format strings!

TypeScript:

const name = "Alice";
console.log(`Hello, ${name}!`); // Template literal

Rust:

let name = "Alice";
println!("Hello, {}!", name); // Format string

TypeScript:

const obj = { name: "Alice", age: 30 };
console.log(obj); // Pretty print in browser/Node.js
console.log(JSON.stringify(obj)); // Manual serialization

Rust:

let tuple = ("Alice", 30);
// println!("{}", tuple); // Error
println!("{:?}", tuple); // Debug format
println!("{:#?}", tuple); // Pretty debug

TypeScript:

const n = 42;
console.log(n.toString(16)); // "2a" (hex)
console.log(n.toString(2)); // "101010" (binary)

Rust:

let n = 42;
println!("{:x}", n); // 2a (hex)
println!("{:b}", n); // 101010 (binary)

Problem:

let name = "Alice";
println!("Hello, name!"); // Prints literally "Hello, name!"

Solution:

let name = "Alice";
println!("Hello, {}!", name); // "Hello, Alice!"

Pitfall 2: Type Doesn’t Implement Display

Section titled “Pitfall 2: Type Doesn’t Implement Display”

Problem:

let tuple = (1, 2);
println!("{}", tuple); // Error: tuple doesn't implement Display

Solution:

let tuple = (1, 2);
println!("{:?}", tuple); // Use debug format

Problem:

println!("{} and {}", 42); // Error: 2 placeholders, 1 argument

Solution:

println!("{} and {}", 42, 100); // Match placeholders

Problem:

let s = String::from("hello");
println!("{}", &s); // Works, but...

Why it works: Rust automatically dereferences for Display.

Better:

let s = String::from("hello");
println!("{}", s); // Clearer

Implement Display for everything:

struct Point { x: i32, y: i32 }
// Lots of boilerplate to implement Display...

Use derive Debug:

#[derive(Debug)]
struct Point { x: i32, y: i32 }
let p = Point { x: 10, y: 20 };
println!("{:?}", p); // Point { x: 10, y: 20 }

Unclear:

println!("User {} (ID: {}) logged in at {}", name, id, time);

Clear:

println!("User {name} (ID: {id}) logged in at {time}");

Tip: Since Rust 2021, named placeholders capture identifiers from the surrounding scope directly. The older explicit form println!("{name}", name = name) is exactly what clippy::uninlined_format_args flags, with the suggestion to drop the redundant name = name argument.

Concatenation:

let s = "Hello, ".to_string() + name + "!";

Use format!:

let s = format!("Hello, {}!", name);

Hard to read:

println!("{:?}", complex_data);

Pretty print:

println!("{:#?}", complex_data);

TypeScript:

function log(level: string, message: string) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${level}: ${message}`);
}
log("INFO", "Server started");
log("ERROR", "Connection failed");

Rust:

fn log(level: &str, message: &str) {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
println!("[{}] {}: {}", timestamp, level, message);
}
fn main() {
log("INFO", "Server started");
log("ERROR", "Connection failed");
}
fn print_table(users: &[(&str, u8, f64)]) {
println!("{:<15} {:>5} {:>10}", "Name", "Age", "Score");
println!("{:-<32}", ""); // Separator
for (name, age, score) in users {
println!("{:<15} {:>5} {:>10.2}", name, age, score);
}
}
fn main() {
let users = [
("Alice", 30, 95.5),
("Bob", 25, 87.3),
("Charlie", 35, 92.0),
];
print_table(&users);
}

Output:

Name Age Score
--------------------------------
Alice 30 95.50
Bob 25 87.30
Charlie 35 92.00

SpecifierDescriptionExampleOutput
{}Displayprintln!("{}", 42)42
{:?}Debugprintln!("{:?}", x)(1, 2)
{:#?}Pretty debugprintln!("{:#?}", x)Multi-line
{:b}Binaryprintln!("{:b}", 42)101010
{:x}Lowercase hexprintln!("{:x}", 42)2a
{:X}Uppercase hexprintln!("{:X}", 42)2A
{:o}Octalprintln!("{:o}", 42)52
{:p}Pointerprintln!("{:p}", &x)0x7fff…
{:e}Lowercase exponentialprintln!("{:e}", pi)3.14159e0
{:E}Uppercase exponentialprintln!("{:E}", pi)3.14159E0
{:.N}Precision (N digits)println!("{:.2}", pi)3.14
{:W}Width (W characters)println!("{:5}", 42)” 42”
{:<W}Left alignprintln!("{:<5}", 42)”42 “
{:>W}Right alignprintln!("{:>5}", 42)” 42”
{:^W}Center alignprintln!("{:^5}", 42)” 42 “
{:0W}Zero padprintln!("{:05}", 42)00042
{:+}Always show signprintln!("{:+}", 42)+42


Print “Hello, Rust!” three times:

fn main() {
// Your code here
}
Solution
fn main() {
println!("Hello, Rust!");
println!("Hello, Rust!");
println!("Hello, Rust!");
}

Print a person’s name and age in format: “Alice is 30 years old”

fn main() {
let name = "Alice";
let age = 30;
// Your code here
}
Solution
fn main() {
let name = "Alice";
let age = 30;
println!("{} is {} years old", name, age);
}

Format π (3.14159) with 2 decimal places:

fn main() {
let pi = 3.14159;
// Your code here
}
Solution
fn main() {
let pi = 3.14159;
println!("{:.2}", pi); // 3.14
}

Print a vector using debug format:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Your code here
}
Solution
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
println!("{:?}", numbers); // [1, 2, 3, 4, 5]
}

Create a string “User: Alice, Score: 95.5” using format!:

fn main() {
let name = "Alice";
let score = 95.5;
let s = /* your code */;
println!("{}", s);
}
Solution
fn main() {
let name = "Alice";
let score = 95.5;
let s = format!("User: {}, Score: {}", name, score);
println!("{}", s);
}

What you’ve learned:

  • println! and print! macros
  • Format placeholders ({}, {:?}, etc.)
  • Number formatting (precision, width, padding)
  • Debug vs Display formatting
  • format! macro for string building
  • Named and positional parameters

Key macros:

println!() // Print with newline
print!() // Print without newline
eprintln!() // Print to stderr
format!() // Create formatted String

Common patterns:

println!("{}", x); // Display
println!("{:?}", x); // Debug
println!("{:#?}", x); // Pretty debug
println!("{:.2}", pi); // Precision
println!("{:10}", n); // Width

Formatting is powerful and type-safe in Rust!