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); } }