Skip to content

Formatting output with print! and println! macros in Rust

Posted on:October 23, 2023 at 11:22 AM

1. Overview

In this tutorial, we’re going to demonstrate different ways to format output with print! and println! macros in Rust.

2. Syntax

Both print! and println! macros are defined in the standard library. They are used to print output to the console. The only difference between them is that println! adds a newline at the end of the output, while print! doesn’t.

Let’s look at the most commonly used macro to print output to the console before we dive into the details:

println!("Hello, world!");

This will print Hello, world! to the console followed by a newline.

3. Basic Formatting

Not like Java or C, we don’t use % to format output in Rust. Instead, we use placeholders and provide values to be inserted into the string.

The placeholder {} will be replaced by the associated value. Example:

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

This will print Hello, Alex! to the console.

Positional Arguments

You can specify the position of arguments using numbers. Example:

println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");

This will print Alice, this is Bob. Bob, this is Alice to the console.

Named Arguments

You also can provide names for your arguments. Example:

println!("{subject} {verb} {object}",
    object="the lazy dog",
    subject="the quick brown fox",
    verb="jumps over");

This will print the quick brown fox jumps over the lazy dog to the console.

4. Special Formatting

Rust provides various traits to format output, such as {:?} for Debug trait, {:x} for hexadecimal, {:p} for pointers, etc. The most commonly used one would be the Debug trait, which is used to print debug information about a value.

println!("{:?} is the debug representation", Some("value"));

This will print Some("value") is the debug representation to the console. Note: Some already implements the Debug trait, so we don’t need to implement it ourselves.

For structs and enums, you can derive the Debug trait to quickly enable printing using println!. Example:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 10, y: 20 };
println!("{:?}", point);

This will print Point { x: 10, y: 20 } to the console.

5. String formatting: Padding

In Rust, you can specify the formatting options after a :.

Minimum Width

You can specify the minimum width of the output using a number after :.

let name = "Alex";
println!("Hello, {:10}!", name);

This will print Hello, Alex ! to the console.

Left Alignment

By default, strings are left-aligned. But you can explicitly specify it using < after :.

let name = "Alex";
println!("Hello, {:<10}!", name);

This will still print Hello, Alex ! to the console.

Right Alignment

You can specify right alignment using > after :.

let name = "Alex";
println!("Hello, {:>10}!", name);

This will print Hello, Alex! to the console.

Center Alignment

You can specify center alignment using ^ after :.

let name = "Alex";
println!("Hello, {:^10}!", name);

This will print Hello, Alex ! to the console.

Custom Padding Character

To pad with a character other than a space, you can combine alignment with a specified character. Below is an example of padding with =:

let name = "Alex";
println!("Hello, {:=>10}!", name);

This will print Hello, ======Alex! to the console.

6. String formatting: Truncating

You can truncate strings using . after :.

let name = "Hello, Rust!";
println!("{:.5}", name);

This will print Hello to the console.

Combine Truncating and Padding

You can combine truncating and padding to get the desired output.

let name = "Hello, Rust!";
println!("{:10.5}", name);

This will print Hello to the console.

Right Alignment with Truncating

Similarly, you can combine right alignment and truncating.

let name = "Hello, Rust!";
println!("{:=>10.5}", name);

This will print =====Hello to the console.

7. Number Formatting

You can format numbers using :b for binary, :o for octal, :x for hexadecimal, and :e for scientific notation.

let number = 255;
println!("Binary: {:b}", number);
println!("Octal: {:o}", number);
println!("Hexadecimal: {:x}", number);
println!("Scientific notation: {:e}", number);

This will print:

Binary: 11111111
Octal: 377
Hexadecimal: ff
Scientific notation: 2.55e2

Precision

For floating-point numbers, you can specify the precision using :.2 after :.

let number = 3.1415926;
println!("Pi: {:.2}", number);

This will print Pi: 3.14 to the console.

Fixed Point Notation

You can use :.* to format floating-point numbers in fixed point notation.

let number = 3.1415926;
println!("Pi: {:.*}", 3, number);

This will print Pi: 3.142 to the console.

8. Date and Time Formatting

In Rust, date and datetime formatting is typically provided by the chrono crate, which is one of the most popular date and time libraries in Rust ecosystem.

Example: First, add chrono to your Cargo.toml:

[dependencies]
chrono = "0.4"

Then, use chrono to format date and time:

extern crate chrono;
use chrono::{NaiveDate, Utc, Local, DateTime};

fn main() {
    // Current date and time in UTC
    let now: DateTime<Utc> = Utc::now();
    println!("{}", now.format("%Y-%m-%d %H:%M:%S")); // e.g., "2023-10-23 14:45:34"

    // Current date and time in local time
    let local_now: DateTime<Local> = Local::now();
    println!("{}", local_now.format("%Y-%m-%d %H:%M:%S")); 

    // Specified date
    if let Some(date) = NaiveDate::from_ymd_opt(2023, 10, 20){
        println!("{}", date.format("%A, %B %d, %Y"));
    }
     // e.g., "Friday, October 20, 2023"
}

In the above example, we used format method to format date and time. The format method accepts a string as an argument, which contains the formatting directives. The formatting directives are prefixed with % and are similar to those used in C’s strftime function.

For a complete list of formatting directives, please refer to chrono’s documentation.

9. Conclusion

In this article, we learned how to format output with print! and println! macros in Rust. It’s essential to remember that print! and println! are macros, not functions, hence the ! at the end of their names. This distinction is vital in Rust because macros can do things that functions can’t, like taking a variable number of arguments. If you’re coming from languages like Python or Java, Rust’s print macros provide a similar but slightly different approach to formatted output. It might take a bit of getting used to, but the flexibility and safety they offer are well worth it.