2.20 Format

Rust’s format! is used as a means of concatenating strings and numbers alike. It lies out the foundation for which other macros such as print! builds on, as format! puts together any amount of variables and nicely returns them bundled up in a String matching our criteria.

This is how it works:

let a = 12
let b = "sheep";
let c = format!("{} {}", a, b);

The c, which is of type String, now contains “12 sheep!”.

It’s also possible to achieve the same result without any other variables than c, even without c if one had wanted to:

let c = format!("{} {}", 12, "sheep!")

This works just as well, although you probably will use the former most of the time: or perhaps even some sort of combination between the two.

Structures

You may print out different types of structures using {:?} inside format!’s string (its first argument). Imagine we wish to quickly print out a vector so that we can go over all of its contents:

let a = vec![2, 3, 2, 1];

println!("{:?}", a); // ‘println!’ builds on format!

Upon executing this, we’ll be presented with:

[2, 3, 4, 1]

This works just as well for tuples as it does vectors. But is it not strange how it works for vectors? How can Rust know how the string of a struct is supposed to look? Let us first have a quick look at the formatting traits.

Formatting traits

Formatting traits are used for all formatting on any type. Whether it be an integer or a tuple, they all have a trait implemented allowing them to be formatted into a string of text.

By using {some_value}, you’re asking for the placeholder to provide formatting for a certain type. All the following, are traits called upon using {some_value}:

Formatting traits are in and of themself incredibly easy to use, as it’s as simple as typing them inside the first argument of a format or alike to have them get to work. But for types you have created yourself, that yet does not implement any of the aforementioned types, it will produce us an error. This, for instance, will not work:

struct Person
{
    age: i32, 
    beard_length: i32
}

fn main()
{
    let person = Person { age: 32, beard_length: 78 };

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

It’s however extremely easy for us to get this to work by printing out the values inside our struct. There is a trait which may be implemented into any struct by deriving it:

#[derive(Debug)]
struct Person // ...

By entering #[derive(Debug)] just before our struct, it will automatically set everything up for us to print it out. All of a sudden, the above example will work and our console will print out:

Person { age: 32, beard_length: 78 }

The derive is used to do a quick implementation of already existing traits with a sort of blueprint for how its methods are supposed to look. This does not work for all structs, but rather those supporting the certain trait. Debug does work with all structs, and are recommended by the Rust team to be used on all structs accessing the public domain.

Positional parameters

Upon adding a number inside format!’s placeholder, it will pick the argument in order that number corresponds to:

println!("{1}, {2}, {0}", 2, 1, 3);

This will print:

1 3 2

We could also combine positional parameters with unnumbered types also derived from the Display trait:

println!("{} {1} {} {0}", 1, 2);

This will print:

1 2 2 1

The reason this occurs is because positional {} picks from the same order they usually would, whilst non-positional {} does not mess the positional order up.

Named parameters

There is also a possibility of naming parameters:

println!("{babe} is driving {n} {car}s simultaneously.", n = 32, babe="Clarissa", car="lamborghini");

This will print:

Clarissa is driving 32 lamborghinis simultaneously.

How nice, she sure has a lot of arms. I mean, how else would she be able to drive so many cars simultaneously?

Formatting traits with parameters

You can combine positional or named parameters with formatting traits:

println!("{0:?}", person);
println!("{p:?}", p = person);

Both these examples are valid. Both makes use of the Person as defined earlier, the first one retrieves person by 0 and the second one using p as a name. In turn, they print out the value using the Debug trait.

You can’t, on the other hand, create two placeholders connected with different traits for the same type:

println!("{0:?} {}", person);

Even if both the traits had been valid for person, Rust would have generated an error.

Formatting parameters

Moreover, rather than using ‘formatting traits with parameters’, we can choose to use a list of different things. That is, rather than {0:?} we can use something other for a format specification {0:format_spec}:

  • + → Using this will ensure a plus is printed out prior to any number. Due to how numbers generally are not printed out, it can prove useful every once in a while.
  • - → Not used at all, as of now. Is probably going to serve a similar purpose as + in the future.
  • 0 → Precedes following number to be printed with zeroes. If the number to be printed is negative, one less zero will print, as this will give numbers the all numbers printed the same size: {:04} (a number preceded by zeros by the length of four).

Alternate forms of printing

The # followed by the alternate form, we can print out things in sexier ways than ever (also used as a formatting paramter):

  • #? → Formats to a version of Debug (the ?) looking more like the version you have in your code.
  • #o → Octal version of a number.
  • #b → Binary version of a number.
  • #x → Hexadecimal version of a number in lowercase.
  • #X → Hexadecimal version of a number in uppercase. Seriously, there needs be no link for this one.

Alignment

As for strings using 0 to add multiple zeros to the beginning of a number, we can decide on where we want that alignment to take place:

  • < → Float it to the left.
  • ^ → Center it.
  • > → Float it to the right.

This is how alignment may be used:

println!("{:^08}", 32);

The output will be:

00032000

Look at how nicely we centered that number! But what if we did not want the zeros? Let us sovle that:

println!("{:^32}", 32);

We sure centered that number nicely! We can even do the same with strings:

println!("{:^99}", "Hello, world!");

And we have a centered string in blankspace! But what if we wanted it enclosed in a given character? This can prove useful when printing and designing small menus:

println!("{:-^99}", "Hello, world!");

This will print:

-------------------------------------------Hello, world!-------------------------------------------

Where the - is placed, you can place whatever symbol you would like to be repeated inside the centering of a string or number. There is actually a syntax for how this works, as well as what order different operations come in.

Syntax

The syntax, as according to Rust’s documentation goes like:

[[fill]align][sign]['#']['0'][width]['.' precision][type]

That is, as for whatever comes after the : inside the string of a format!.

  • fill → The character that will be used in the padding, like - in the previous example.
  • align → Type of alignment: <, ^ or >.
  • sign → + or -.
  • # → The alternate forms of printing.
  • 0 → Preceeds number with zeros.
  • width → Amount of symbol or zeros for number or string to be centered within.
  • '.' precision → Define precision for a number to be printed out, .2 would mean that you would like two decimals to be shown from a floating-point number.
  • type → Referring to the format traits.

Precision moreover

Precision has some additional features moreover, than just being able to specify the amounts of decimals to be shown from a floating-point number.

  • .integer → Round to nearest number containing integer_name of decimal places.
  • .integer_or_name$ → Round to nearest number containing integer_or_name of decimal places, alternatively name that parameter given to format!.
  • .* → Round to nearest number based on two parameters, the first one for amount of decimal places, the second one for the floating-point number. If you were to input {2:.*} it would refer to that parameter two entered is the floating-point number and parameter one is the number of decimal places.

Examples:

println!("{:.3}", 32.432);
println!("{num:.pre$}", pre=3, num=32.432);
println!("{} {2:.*}", "i am ", 3, 32.432);

Upon being ran, this will print:

32.432
32.432
i am 32.432

Escaping

The { and } may get in the way at times. What if you wanted to print them out, for instance? You can do this by escaping them with another character identical to itself. That is, {{ will print { and }} will print }.

Exercises

  • Print out "I am a girl, yet 3.74 people out of 63723.2 say I am not." using as many variables and precisions as you possibly can.
  • Print out a struct using {:#?} and be amazed at how beautiful it looks.

Moreover

Although format! is quite similar to that of printing in languages such as Python, it makes a serves as a really powerful tool when it comes to systems langauges like Rust. With the use from format! and macros using it such as print! and println!, both development of text-based applications as debugging when writing graphical ones gets part of the pain out of the way.

Traits
Drop