Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust Explanations

This chapter collects short explanations of Rust ideas that are easy to misunderstand in an interview.

Display as an Abstraction

Consider this function:

#![allow(unused)]
fn main() {
fn print_twice<T: std::fmt::Display>(value: T) {
    println!("{value}");
    println!("{value}");
}
}

At first glance, it can look like the trait is unnecessary because the function is just calling println! twice.

But the repetition is not the point.

  • Writing println! twice only repeats the action.
  • The Display trait tells Rust what kinds of values this function can print.
  • Display is the abstraction that says: "this type knows how to present itself as human-readable text."

So this function is really expressing two separate ideas:

  1. repeat the printing action twice
  2. accept any value whose type implements Display

Without the trait bound, this function would have to be hardcoded to a specific type:

#![allow(unused)]
fn main() {
struct User {
    name: String,
}

fn print_user_twice(user: &User) {
    println!("{}", user.name);
    println!("{}", user.name);
}
}

That only works for User.

With the trait bound, the same function works for many different types:

fn main() {
    print_twice(42);
    print_twice("hello");
    print_twice(String::from("rust"));
}

And if a custom type implements Display, it works too:

#![allow(unused)]
fn main() {
use std::fmt;

struct User {
    name: String,
}

impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "User({})", self.name)
    }
}

fn print_twice<T: fmt::Display>(value: T) {
    println!("{value}");
    println!("{value}");
}
}

Why println! Needs Display

This does not work:

#![allow(unused)]
fn main() {
// println(user);
}

This does:

#![allow(unused)]
fn main() {
println!("{}", user);
println!("{user}");
}

That is because println! is a formatting macro. The {} form uses the Display trait implementation of the type.

So Display is not about "printing twice." It is about making the type printable in a human-readable way.

What the Generic Means

This function:

#![allow(unused)]
fn main() {
fn print_twice<T: std::fmt::Display>(value: T)
}

means:

  • the function is generic over some type T
  • T must implement Display
  • value is a value of type T

You can read it as:

For any concrete type T, as long as T implements Display, this function can accept a value of type T.

This version means almost the same thing:

#![allow(unused)]
fn main() {
fn print_twice(value: impl std::fmt::Display)
}

That can be read as:

value has some concrete type that implements Display.

Why It Is Called a Generic Type

Yes, this is part of why it is called a generic type.

The function is not written for one concrete type like User or String. It is written generically, meaning it works over a family of types.

The trait bound narrows that family.

So:

  • T is generic because it can stand for many concrete types
  • T: Display constrains that generic type to only the types that implement Display

That is what makes the function reusable without losing type safety.

Mental Model

  • println! twice = repetition
  • Display = formatting capability
  • T = some concrete type chosen at compile time
  • T: Display = that type must support human-readable formatting

Put together:

Repeat the printing action twice for any type that knows how to display itself.

Short Interview Answer

The point of the Display trait there is not the repetition. The repetition comes from calling println! twice. The trait bound is what makes the function generic: it says the function can accept any concrete type, as long as that type knows how to format itself for {} output.