Skip to content
>_rs4ts

Output and Formatting

11 min readupdated byAhmet ZeybekAhmet Zeybek· Jul 10, 2026

Learn how to print output in Rust using the println! macro and its rich 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 expressive 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 expressive and type-safe in Rust!