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
Displaytrait tells Rust what kinds of values this function can print. Displayis the abstraction that says: "this type knows how to present itself as human-readable text."
So this function is really expressing two separate ideas:
- repeat the printing action twice
- 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 Tmust implementDisplayvalueis a value of typeT
You can read it as:
For any concrete type
T, as long asTimplementsDisplay, this function can accept a value of typeT.
This version means almost the same thing:
#![allow(unused)] fn main() { fn print_twice(value: impl std::fmt::Display) }
That can be read as:
valuehas some concrete type that implementsDisplay.
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:
Tis generic because it can stand for many concrete typesT: Displayconstrains that generic type to only the types that implementDisplay
That is what makes the function reusable without losing type safety.
Mental Model
println!twice = repetitionDisplay= formatting capabilityT= some concrete type chosen at compile timeT: 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
Displaytrait there is not the repetition. The repetition comes from callingprintln!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.