10 - Generic Types, Traits and Lifetimes Flashcards

1
Q

Write generic function named “largest” that gets any type of list with comparable elements and returns the largest element in it.

A
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)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What is the difference between Partial Ordering and Total Ordering?
What does cmp::PartialOrd must satisfy in Rust?

A

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:

  1. Transitivity: a<b></b>
  2. Duality: a<b>a</b>

See more in:
https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html</b></b>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Write a generic struct of a 2D point. Implement a getter for one of its elements (gets a reference only, not mutable)

A
struct Point {
  x: T,
  y: T,
}
impl Point {
  fn x(&self) -> &T {
    &self.x
  }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What is Monomorphization? And how is it related to Rust generics? What is the benefit?

A
  1. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
  2. 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.
  3. The process of monomorphization makes Rust’s generics extremely efficient at runtime.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
  1. 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.
  2. 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.
A
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)
  }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What is the orphan rule in regard to traits?
Which general property includes this rule?
What is the purpose of this rule?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Is it possible to call the default implementation from an overriding implementation of that same method?

A

No.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Write the following trait bound in a function differently.

pub fn notify(item: &impl Summary) {
println!(“Breaking news! {}”, item.summarize());
}

A

pub fn notify(item: &T) {
println!(“Breaking news! {}”, item.summarize());
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Write the following trait bound in a function differently (2 ways).

pub fn notify(item1: &T, item2: &U) {…}

A
//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
{...}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

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 {...}}
}
A

Can’t use “impl Summary” to return multiple types. It can only return a single type that implements Summary.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Why can we use to_string() in the following code?

let s = 3.to_string();

A

we can turn integers into their corresponding String values like this because integers implement Display

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q
What is the meaning of the lifetime annotations in the following function signature:
fn longest(x: &'a str, y: &'a str) -> &'a str {...}
A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What is the meaning of the lifetime annotations in the following struct signature:
struct ImportantExcerpt {
part: &’a str,
}

A

This annotation means an instance of ImportantExcerpt can’t outlive the reference it holds in its part field

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What are the three elision rules?

A
  1. each parameter that is a reference gets its own lifetime parameter.
  2. if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
  3. 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.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Why do lifetime names for struct fields always need to be declared after the impl keyword and then used after the struct’s name?

A

Because those lifetimes are part of the struct’s type.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

what is the implicit lifetimes in the following code?

impl ImportantExcerpt {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}
A

There are two input lifetimes, so Rust applies the first lifetime elision rule and gives both &self and announcement their own lifetimes. Then, because one of the parameters is &self, the return type gets the lifetime of &self, and all lifetimes have been accounted for.

17
Q

What is the lifetime of all string literals? and why?

A

All string literals have the ‘static lifetime, which means that this reference can live for the entire duration of the program.

The text of the string is stored directly in the program’s binary, which is always available. Therefore, the lifetime of all string literals is ‘static.