# Generic Types, Traits, and Lifetimes ### Speaker: @tigercosmos --- ## generics - `Option<T>` - `Vec<T>` - `HashMap<K, V>` ---- ### Why we need generics? duplicated functions ```rust fn largest_i32(list: &[i32]) -> i32 {} fn largest_char(list: &[char]) -> char {} ``` how about ```rust fn largest<T>(list: &[T]) -> T {} ``` --- ## Generic Data Types ---- ### Function Definition ```rust fn largest<T>(list: &[T]) -> T {} ``` ---- ### Stuct Definition ```rust struct Point<T> { x: T, y: T, } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; } ``` note: `x` and `y` in `Point` must be the same type or causes error. ---- Two generics in a `Struct` is fine ```rust struct Point<T, U> { x: T, y: U, } fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 }; } ``` ---- ### Enum Definitions ```rust enum Option<T> { Some(T), None, } ``` ```rust enum Result<T, E> { Ok(T), Err(E), } ``` ---- ### Method Definitions ```rust struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); } ``` Note: declare `T` just after `impl` to specify implementing methods on the type `Point<T>` ---- Not work. why? ```rust impl Point<T> { fn x(&self) -> &T { &self.x } } ``` need bring scope in `impl` ---- ### concrete type for the generic type parameter `T` We don't add type after `impl` if we don't need generics from `Struct` ``` impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } ``` ---- Other types in `impl` different from types of `Struct` are allowed. ```rust struct Point<T, U> { x: T, y: U, } impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } } fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c'}; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y); } ``` ---- ## Perforamnce of Code Using Generics Monomorphization 單態化 ```rust let integer = Some(5); let float = Some(5.0); ``` turn out to be ```rust 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); } ``` Note: `template` in C++ has a similar way. --- ## Traits: Defining Shared Behavior ---- ### Definition - `trait` keyword - declare many methods in the trait - each type which uses the trait needs to provide its own custom behavior body for the method. ```rust pub trait Summary { fn summarize(&self) -> String; } ``` ---- ```rust pub struct NewsArticle { pub headline: String, // ... } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } pub struct Tweet { pub username: String, // ... } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } } ``` ---- Call the method just as regular methods, such as `tweet.summarize()` ```rust let tweet = Tweet { username: String::from("tigercosmos"), // ... }; println!("1 new tweet: {}", tweet.summarize()); ``` ---- - trait needs to be imported if not in scope - trait must be `pub` if it would be used otherwhere ---- ### coherence rule traits could be used for local types. external traits on external types is forbidden. Popular traits: Display, Debug, ToString, Default, should be implemented in a crate, becasue others cannot implement theirselves. ---- ### Fully Qualified Syntax `<T as TraitName>::item` ```rust trait Cook { fn start(&self); } trait Wash { fn start(&start); } struct Chef; impl Cook for Chef { fn start(&seld) { println!("Cook Start"); } } impl Wash for Chef { fn start(&seld) { println!("Cook Start"); } } fn main { let me = Chef; <Cook>::start(&me); <Chef as Wash>::start(&me); } ``` ---- ### Default implementations define the method when declare the trait ```rust pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } } let article = NewsArticle { // ..... }; println!("New article available! {}", article.summarize()); // New article available! (Read more...) ``` ---- `trait` could call other methods in the same trait or the struct member ```rust pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) } } impl Summary for Tweet { fn summarize_author(&self) -> String { format!("@{}", self.username) } } fn main() { let tweet = Tweet { username: String::from("tigercosmos"), content: String::from("You should follow @tigercosmos's github :)"), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize()); } ``` try yourself: [playground link](https://play.rust-lang.org/?gist=b70e8f150a2d2a231e9a88d74c4f5920&version=stable&mode=debug&edition=2015) ---- ### Trait Bounds specify that `item` must be of a type that implements the `Summary` trait: ```rsut pub fn notify<T: Summary>(item: T) { println!("Breaking news! {}", item.summarize()); } ``` ---- multiple trait bounds on a generic type using the `+` syntax ```rust fn some_function< T: Display + Clone, U: Clone + Debug > (t: T, u: U) -> i32 { } ``` or using `where` ```rust fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug { } ``` ---- ### Derive an attribute for automatically adding `impl` of `trait` for `Struct` ```rust #[derive(Copy, Clone, Default, Debug)] Struct Foo { } ``` ```rust impl Copy for Foo { /*...*/ }; impl Clone for Foo { /*...*/ }; impl Default for Foo { /*...*/ }; impl Debug for Foo { /*...*/ }; ``` ---- ### Trait Alias ```rust pub trait Service { type Request; type Response; type Error; }; trait HttpService = Service< Request = http::Request, Reponse = http::Response, Error = http::Error >; ``` --- ## Conditionally implement method `impl` could be many blocks. Generics and trait bounds make sense for many block of implement method. ```rust impl<T, U> Point<T, U> {} impl<T> Point<T, U> {} impl Point<T, U> {} impl<T: Display + PartialOrd> Point<T> {} ``` ---- ### Blanket implementations conditionally implement a trait for any type that implements another trait. ```rust impl<T: Display> ToString for T { // --snip-- } ``` --- ## STD traits - Display: enable printing via `{}` - Debug: enable printing via `{:?}` `{:#?}` - ToString: output "Display" to string - Default: return `Self` ---- PartialOrd/Ord/PartialEq/Eq ``` For a, b, c in a set X 1. Antisymmetry: if a < b, !(a>b); if a > b, then !(a < b) 2. Transitivity: a > b, b > c, then a > c 3. Completeness: all elements in X must be one of relationships a > b, a < b, or a == b partail order: fit 1, 2 total order: fit 1, 2, 3 ``` ```rust let nan = std::f32:NAN; let x = 1.9f32; println("{}", nan < x); // false println("{}", nan > x); // false println("{}", nan == x); // false // IEEE754 ``` --- ### Example of Applying traits I want to have a generic function that finds the largest one in `list` ```rust fn largest<T>(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest } fn main() { let number_list = vec![34, 50, 25, 100, 65]; println!("The largest number is {}", largest(&number_list)); let char_list = vec!['y', 'm', 'a', 'q']; println!("The largest char is {}", largest(&char_list)); } ``` ``` note: `T` might need a bound for `std::cmp::PartialOrd` ``` ---- Add `<T: PartialOrd + Copy>` here ```rust fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest } ``` try yourself: [link](https://play.rust-lang.org/?gist=ea6a9e77fbd366e6af75bf8c20c19a92&version=stable&mode=debug&edition=2015) ---- ### How can I implement `PartialOrd`? [rust document](https://github.com/rust-lang/rust/blob/4cf11765dc98536c6eedf33f2df7f72f6e161263/src/libcore/cmp.rs#L615) ```rust use std::cmp::Ordering; #[derive(Eq)] struct Person { /* fields */} impl PartialOrd for Person { fn partial_cmp(&self, other: &Person) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Person { fn cmp(&self, other: &Person) -> Ordering { self.height.cmp(&other.height) } } impl PartialEq for Person { fn eq(&self, other: &Person) -> bool { self.height == other.height } } ``` try yourself: [link](https://play.rust-lang.org/?gist=311513bf1b9f088ec7f5451b20e2d105&version=stable&mode=debug&edition=2015) ---- ### Rust Generics VS C++ template Rust checks right away C++ check while instantiating --- ## Lifetime ---- ### How Rust Prevent Dangling References ```rust { let r; { let x = 5; r = &x; } println!("r: {}", r); } ``` ``` --> src/main.rs:7:5 | 6 | r = &x; | - borrow occurs here 7 | } | ^ `x` dropped here while still borrowed ... 10 | } | - borrowed value needs to live until here ``` ---- ### The Borrow Checker ```rust { let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!("r: {}", r); // | } // ---------+ ``` ---- The correct one ```rust { let x = 5; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println!("r: {}", r); // | | // --+ | } // ----------+ ``` ---- ### Generic Lifetimes in Functions ```rust fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } ``` ```error[E0106]: missing lifetime specifier --> src/main.rs:1:33 | 1 | fn longest(x: &str, y: &str) -> &str { | ^ expected lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` ``` ---- ### Lifetime Annotation Syntax ```rust &i32 // a reference &'a i32 // a reference with an explicit lifetime &'a mut i32 // a mutable reference with an explicit lifetime ``` annotations are meant to tell Rust how generic lifetime parameters of multiple references relate to each other. ---- A fixup for previous one ```rust fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` ---- We don't need to declare lifetime if it has not "relationship to x" or "return value" ```rust fn longest<'a>(x: &'a str, y: &str) -> &'a str { x } ``` ---- the lifetime parameter for the return type needs to match the lifetime parameter for one of the parameters ```rust fn longest<'a>(x: &str, y: &str) -> &'a str { let result = String::from("really long string"); result.as_str() } ``` ``` 3 | result.as_str() | ^^^^^^ does not live long enough 4 | } | - borrowed value only lives until here ``` ---- ### Lifetime Annotations in Struct Definitions ```rust struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.') .next() .expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence }; } ``` ---- ### Lifetime Elision ```rust fn first_word<'a>(s: &'a str) -> &'a str { } ``` no need lifetime specification here ---- ### Three rules - each parameter that is a reference gets its own lifetime parameter ```rust fn foo<'a, 'b>(x: &'a i32, y: &'b i32) ``` - if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters ```rust fn foo<'a>(x: &'a i32) -> &'a i32 ``` - if there is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime ---- ### Apply Three Rule ```rust // origin fn first_word(s: &str) -> &str { } // first rule fn first_word<'a>(s: &'a str) -> &str { } // second rule fn first_word<'a>(s: &'a str) -> &'a str { } ``` ```rust fn foo<'a>(s: &'a str) -> &'a str { } ``` ---- ### Static Lifetime `'static` means the life is the entire duration of the program ```rust fn foo<'a>() -> &'a str { "I have a static lifetime." } fn main() { println!("{}", foo()); } ``` ```rust fn foo() -> &'static str { "I have a static lifetime." } fn main() { println!("{}", foo()); } ``` --- ## Combination ```rust use std::fmt::Display; fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } } ``` --- # The End
{"metaMigratedAt":"2023-06-14T18:22:31.954Z","metaMigratedFrom":"Content","title":"Generic Types, Traits, and Lifetimes","breaks":true,"contributors":"[{\"id\":\"b4ba298e-5cbb-419b-a0c9-4f2e4612e8bf\",\"add\":18275,\"del\":4333}]"}
    897 views