10 - Generic Types, Traits and Lifetimes Flashcards
Write generic function named “largest” that gets any type of list with comparable elements and returns the largest element in it.
fn largest(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest }
- **PartialOrd is necessary because we’re using ‘>’
- **Copy is necessary because we move list[0] into largest, and we move elements in list into item (of type T)
What is the difference between Partial Ordering and Total Ordering?
What does cmp::PartialOrd must satisfy in Rust?
Total Order is a Partial Order in which any two elements are comparable. In Partial Order there may exists two elements that are not comparable.
A non-strict Partial Order is a relation over some set P which is reflexive, antisymmetric (a<=b means b>=a) and transitive.
In Rust, the comparison cmp::PartialOrd must satisfy for all a, b and c:
- Transitivity: a<b></b>
- Duality: a<b>a</b>
See more in:
https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html</b></b>
Write a generic struct of a 2D point. Implement a getter for one of its elements (gets a reference only, not mutable)
struct Point { x: T, y: T, } impl Point { fn x(&self) -> &T { &self.x } }
What is Monomorphization? And how is it related to Rust generics? What is the benefit?
- Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
- In this process, Rust’s compiler looks at all the places where generic code is called and generates code for the concrete types the generic code is called with.
- The process of monomorphization makes Rust’s generics extremely efficient at runtime.
- Write a trait “Summary” with methods “who” and “summarize” that return a String (both methods).
Return “(Read more from {self.who()})” as the default implementation of summarize. - Implement “Summary” for a struct “Tweet” with a field “username” of type String.
Return username as output to who. Use the default implementation of summarize.
pub trait Summary { fn who(&self) -> String; fn summarize(&self) -> String { String::from("(Read more from {})", self.who()) } }
impl Summary for Tweet { fn who(&self) -> String { format("{}", self.username) } }
What is the orphan rule in regard to traits?
Which general property includes this rule?
What is the purpose of this rule?
We can’t implement external traits on external types. For example, we can’t implement the Display trait on Vec within our crate, because Display and Vec are defined in the standard library and aren’t local to our crate.
This restriction is part of a property of programs called “coherence”, and more specifically the orphan rule, so named because the parent type is not present.
This rule ensures that other people’s code can’t break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use.
Is it possible to call the default implementation from an overriding implementation of that same method?
No.
Write the following trait bound in a function differently.
pub fn notify(item: &impl Summary) {
println!(“Breaking news! {}”, item.summarize());
}
pub fn notify(item: &T) {
println!(“Breaking news! {}”, item.summarize());
}
Write the following trait bound in a function differently (2 ways).
pub fn notify(item1: &T, item2: &U) {…}
//using impl pub fn notify(item1: &(impl Summary + Display), item2: &impl Summary) {...}
or
//using where pub fn notify(item1: &T, item2: &U) where T: Summary + Display, U: Summary {...}
What’s wrong with the following code? (assume both NewsArticle and Tweet implement Summary)
fn returns_summarizable(switch: bool) -> impl Summary { if switch {NewsArticle {...}} else {Tweet {...}} }
Can’t use “impl Summary” to return multiple types. It can only return a single type that implements Summary.
Why can we use to_string() in the following code?
let s = 3.to_string();
we can turn integers into their corresponding String values like this because integers implement Display
What is the meaning of the lifetime annotations in the following function signature: fn longest(x: &'a str, y: &'a str) -> &'a str {...}
it means that both x, y and the returned ref are string slices that live at least as long as lifetime ‘a.
In practice, it means that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in - and we want it to be enforced by the compiler.
What is the meaning of the lifetime annotations in the following struct signature:
struct ImportantExcerpt {
part: &’a str,
}
This annotation means an instance of ImportantExcerpt can’t outlive the reference it holds in its part field
What are the three elision rules?
- each parameter that is a reference gets its own lifetime parameter.
- if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
- if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters.
Why do lifetime names for struct fields always need to be declared after the impl keyword and then used after the struct’s name?
Because those lifetimes are part of the struct’s type.