Hello Generics

Earlier we were looking at this greet function that only accepted a string slice.

fn main() {
    greet("Tim");
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

If we wanted to greet integers, we'd need another function.

fn main() {
    greet(3);
}

fn greet(name: i64) {
    println!("Hello, {}!", name);
}

How do we write a single function that accepts either? That's where generics come in. The basic syntax for declaring a generic function looks like this.


#![allow(unused_variables)]
fn main() {
fn greet<T>(name: &T) {
    println!("Hello, {}!", name);
}
}

That T in angle brackets stands for any type. Then our variable name is a reference to whatever that type is. But this isn't going to work because we're trying to format that variable with {}. Earlier we mentioned that this depends on the Display trait. Thus our function isn't completely generic. We can't accept any type T. We can only accept types that satisfy the Display trait. We can do that by adding a trait bound like so. The Display trait is defined in the standard library, so first we use it.


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

fn greet<T: Display>(name: &T) {
    println!("Hello, {}!", name);
}
}

Now, it turns out that the Display trait implies Sized as well. But we don't care about that if we're just formatting, so we need to relax it like so.

use std::fmt::Display;

fn main() {
   greet("Tim");
   greet(&3);
}

fn greet<T: Display + ?Sized>(name: &T) {
    println!("Hello, {}!", name);
}

Now we can greet string slices, integer references, and anything else that implements Display.

use std::fmt::Display;

fn main() {
    greet("Tim");
    greet(&3);
}

fn greet<T: Display + ?Sized>(name: &T) {
    println!("Hello, {}!", name);
}

Running this gives

Hello, Tim!
Hello, 3!

If the trait bounds get too unwieldy, there is an alternative syntax with a where clause added afterwards.

use std::fmt::Display;

fn main() {
   greet("Tim");
   greet(&3);
}

fn greet<T>(name: &T)
where
    T: Display + ?Sized,
{
    println!("Hello, {}!", name);
}

TMTOWTDI!

Now, the Display trait was required for the {} in the println!. If we wanted to print it with {:?} instead, that would require the Debug trait.

use std::fmt::Debug;

fn main() {
    greet("Tim");
    greet(&3);
}

fn greet<T: Debug + ?Sized>(name: &T) {
    println!("Hello, {:?}!", name);
}

Running this yields

Hello, "Tim"!
Hello, 3!

The 3 printed the same, but notice the quotes around Tim. That can be handy. I often add quotes around things like filenames in Perl.

say "Writing players to file '$path'..." if $opt->verbose;

In Rust, I can format them with {:?} instead of {}.

fn main() {
let path = "players.json";
let verbose = true;
if verbose {
    println!("Writing players to file {:?}...", path);
}
}