Generics
-
Generics allow generalizing functions and types to reduce code duplication.
-
Generics are used in type parameters.
-
Type parameters are declared inside angle brackets and are conventionally uppercase letters (eg.
<T>).
Using Generics
fn main() {
let v1: Vec<i32> = vec![1, 2, 3, 4, 5];
let v2: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let v3: Vec<&str> = vec!["crab", "penguin", "language"];
}
The type of a vector is Vec<T>.
fn main() {
let maybe_int: Option<i32> = Some(5);
let maybe_int_2: Option<i32> = None;
}
The type of an option is Option<T>.
Generics in Structs
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer: Point<i32> = Point { x: 5, y: 10 };
let float: Point<f32> = Point { x: 1.0, y: 4.0 };
}
-
The type
Point<T>is generic over the typeT -
The fields
xandycan be any typeT, but they must be the same type.
Generics in Enums
enum Either<L, R> {
Left(L),
Right(R),
}
fn main() {
let int_or_float: Either<i32, f32> = Either::Left(5);
let int_or_float_2: Either<i32, f32> = Either::Right(100.0);
let int_or_int: Either<i32, i32> = Either::Left(100);
}
-
The type
Either<L, R>is generic over the typesLandR -
LandRcan be the same type, but they don’t have to be!
Generics in Functions
Without generics:
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
Generics in Functions (2)
With generics:
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
-
The function
largestis generic over typeT, which normally can be any type… -
BUT since we want to compare
item > largest, we need to restrict the possible typesTcan be to types that can be compared (implements thePartialOrdtrait)
Generics in Methods
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
fn y(&self) -> &T {
&self.y
}
}
The <T> after impl allows us to refer to the T in Point<T> inside the impl block.
Generics in Methods (2)
Concrete methods can be implemented for generic types by specifying a concrete type for generics:
struct Point<T> {
x: T,
y: T,
}
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
Performance of Generics
Rust performs monomorphization of generic code, meaning the compiler turns generic code into specific code by generating separate code for each concrete type:
let integer = Some(5);
let float = Some(5.0);
is automatically turned at compile time into:
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
Traits
- A type is a set of values and a set of operations we can perform on those values
- The set of operations are the methods we can call on that type
- Different types share the same behavior if we can call the same methods on all of those types
- A trait is a grouping of methods to define a set of behaviors necessary to accomplish some purpose
trait Summary {
fn summarize(&self) -> String;
}
The Summary trait requires implementors to define a method named summarize that takes &self and returns a String.
What the implementor does under the hood is no concern to the trait as long as the method signature is upheld.
Trait Implementation
A struct can implement a trait by using the impl Trait for Type syntax:
pub struct NewsArticle {
pub headline: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {}", self.headline, self.author)
}
}
Trait Implementation (2)
The same trait can be implemented by multipe types:
pub struct Tweet {
pub username: String,
pub content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Default Trait Implementation
Traits can have default implementations:
trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct NewsArticle {
pub headline: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {}
The corresponding implementation block for a type can then be left empty to use the default implementation of the trait.
Default Trait Implementation (2)
Default trait implementations can call other trait methods:
trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct NewsArticle {
pub headline: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize_author(&self) -> String {
format!("Author {}", self.author)
}
}
Trait bounds in functions
Functions can accept “anything that implements a particular trait”. The following are all equivalent:
-
With
impl Traitsyntax:fn print_summary(item: &impl Summary) { println!("Summary: {}", item.summarize()); } -
With generics and trait bounds:
fn print_summary<T: Summary>(item: &T) { println!("Summary: {}", item.summarize()); }
Multiple Trait Bounds
Multiple trait bounds can be specified with the + syntax:
fn print_summary<T: Summary + Display>(item: &T) {
println!("Summary: {}", item.summarize());
}
The type T must implement both the Summary and Display traits.
For functions with many generics and trait bounds, using where clauses can make functions signature more readable:
fn print_summary<T>(item: &T) where T: Summary {
println!("Summary: {}", item.summarize());
}
fn some_function_2<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug
{
// Function body
}
Conditional Methods
Trait bounds can be used to conditionally implement methods for types that implement the specified traits.
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}