Rust Getting started with Rust Advanced usage of println!


println! (and its sibling, print!) provides a convenient mechanism for producing and printing text that contains dynamic data, similar to the printf family of functions found in many other languages. Its first argument is a format string, which dictates how the other arguments should be printed as text. The format string may contain placeholders (enclosed in {}) to specify that a substitution should occur:

// No substitution -- the simplest kind of format string
println!("Hello World");
// Output: Hello World

// The first {} is substituted with a textual representation of
// the first argument following the format string. The second {}
// is substituted with the second argument, and so on.
println!("{} {} {}", "Hello", true, 42);
// Output: Hello true 42

At this point, you may be asking: How did println! know to print the boolean value true as the string "true"? {} is really an instruction to the formatter that the value should be converted to text using the Display trait. This trait is implemented for most primitive Rust types (strings, numbers, booleans, etc.), and is meant for "user-facing output". Hence, the number 42 will be printed in decimal as 42, and not, say, in binary, which is how it is stored internally.

How do we print types, then, that do not implement Display, examples being Slices ([i32]), vectors (Vec<i32>), or options (Option<&str>)? There is no clear user-facing textual representation of these (i.e. one you could trivially insert into a sentence). To facilitate the printing of such values, Rust also has the Debug trait, and the corresponding {:?} placeholder. From the documentation: "Debug should format the output in a programmer-facing, debugging context." Let's see some examples:

println!("{:?}", vec!["a", "b", "c"]);
// Output: ["a", "b", "c"]

println!("{:?}", Some("fantastic"));
// Output: Some("fantastic")

println!("{:?}", "Hello");
// Output: "Hello"
// Notice the quotation marks around "Hello" that indicate
// that a string was printed.

Debug also has a built-in pretty-print mechanism, which you can enable by using the # modifier after the colon:

println!("{:#?}", vec![Some("Hello"), None, Some("World")]);
// Output: [
//    Some(
//        "Hello"
//    ),
//    None,
//    Some(
//        "World"
//    )
// ]

The format strings allow you to express fairly complex substitutions:

// You can specify the position of arguments using numerical indexes.
println!("{1} {0}", "World", "Hello");
// Output: Hello World

// You can use named arguments with format
println!("{greeting} {who}!", greeting="Hello", who="World");
// Output: Hello World

// You can mix Debug and Display prints:
println!("{greeting} {1:?}, {0}", "and welcome", Some(42), greeting="Hello");
// Output: Hello Some(42), and welcome

println! and friends will also warn you if you are trying to do something that won't work, rather than crashing at runtime:

// This does not compile, since we don't use the second argument.
println!("{}", "Hello World", "ignored");

// This does not compile, since we don't give the second argument.
println!("{} {}", "Hello");

// This does not compile, since Option type does not implement Display
println!("{}", Some(42));

At their core, the Rust printing macros are simply wrappers around the format! macro, which allows constructing a string by stitching together textual representations of different data values. Thus, for all the examples above, you can substitute println! for format! to store the formatted string instead of printing it:

let x: String = format!("{} {}", "Hello", 42);
assert_eq!(x, "Hello 42");