Skip to content
Learn Rust
Theme

Checkpoint: Abstractions

This checkpoint is for the Abstractions part of the course (traits, generics, common traits, trait objects, returning references, lifetimes, modules) and the parts before it. Each question is a short program. Read it, decide your answer on paper, and only then open the answer. Every answer is a real run of that exact file with Rust 1.99.1 in the course image, so the compiler text is what you would see.

RememberGuess first. For the error questions, name the code and the line before you look; the code is the part worth remembering.

Rustsrc/bin/q1.rs
trait Pilot {
fn fly(&self) -> String;
}
trait Wizard {
fn fly(&self) -> String;
}
struct Human;
impl Pilot for Human {
fn fly(&self) -> String {
String::from("pilot: this is your captain")
}
}
impl Wizard for Human {
fn fly(&self) -> String {
String::from("wizard: up!")
}
}
impl Human {
fn fly(&self) -> String {
String::from("human: *waves arms*")
}
}
fn main() {
let person = Human;
println!("{}", person.fly());
println!("{}", Pilot::fly(&person));
println!("{}", <Human as Wizard>::fly(&person));
}

Does it compile? What are the three lines?

Answer
terminal
$ cargo run --bin q1
human: *waves arms*
pilot: this is your captain
wizard: up!

A type’s own method wins over a trait’s, so person.fly() picks the one in impl Human. The other two name the trait, with Pilot::fly(&person) or the fully qualified <Human as Wizard>::fly(&person). See Two traits, one method name.

Rustsrc/bin/q2.rs
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
struct Pair {
first: Noisy,
second: Noisy,
}
fn main() {
let _x = Noisy("x");
let _ = Noisy("ignored");
let pair = Pair { first: Noisy("first"), second: Noisy("second") };
let _y = Noisy("y");
println!("pair: {} and {}", pair.first.0, pair.second.0);
println!("end of main");
}

Does it compile? What are the seven lines, in order?

Answer
terminal
$ cargo run --bin q2
drop ignored
pair: first and second
end of main
drop y
drop first
drop second
drop x

let _ = ... does not keep the value, so ignored is dropped on the spot. At the end of main, locals are dropped in the reverse of the order they were made: y, then pair, then x. The fields of pair are dropped in the order they are written, first and then second. See Scopes, drop order and let _ and Drop.

Rustsrc/bin/q3.rs
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Level {
Low,
Mid(u8),
High,
}
fn main() {
let mut levels = vec![Level::High, Level::Mid(5), Level::Low, Level::Mid(2)];
levels.sort();
println!("{levels:?}");
println!("{}", Level::Mid(200) < Level::High);
}

Does it compile? What are the two lines?

Answer
terminal
$ cargo run --bin q3
[Low, Mid(2), Mid(5), High]
true

A derived order compares enum variants by declaration order, and compares the data inside a variant only when both values are the same variant. So every Mid(_) sorts before High, whatever the number, and Mid(2) is before Mid(5). See Equality, order and hashing.

Rustsrc/bin/q4.rs
#[derive(Debug, PartialEq, PartialOrd)]
struct Reading(f64);
fn main() {
let missing = Reading(f64::NAN);
println!("{}", missing == missing);
println!("{:?}", Reading(1.0).partial_cmp(&missing));
println!("{}", Reading(1.0) < missing);
println!("{}", Reading(1.0) == Reading(1.0));
}

Does it compile? What are the four lines?

Answer
terminal
$ cargo run --bin q4
false
None
false
true

NaN is not equal to itself, so the derived == is false for missing == missing. partial_cmp returns an Option<Ordering> and gives None when two values cannot be put in order, and < on a None is false. Two ordinary readings are equal, as you would expect. See Equality, order and hashing.

Rustsrc/bin/q5.rs
struct Parser<'a> {
text: &'a str,
}
fn main() {
let parser;
{
let line = String::from("go north");
parser = Parser { text: &line };
}
println!("{}", parser.text);
}

Does it compile? If not, which error code, and which line does the compiler say line is dropped on, and which line uses the borrow afterwards?

Answer
$ cargo run --bin q5
error[E0597]: `line` does not live long enough
  --> src/bin/q5.rs:9:33
   |
 8 |         let line = String::from("go north");
   |             ---- binding `line` declared here
 9 |         parser = Parser { text: &line };
   |                                 ^^^^^ borrowed value does not live long enough
10 |     }
   |     - `line` dropped here while still borrowed
11 |     println!("{}", parser.text);
   |                    ----------- borrow later used here

Parser<'a> holds a reference, so a Parser cannot live longer than the String it borrows. line is dropped at the closing brace of the inner block (line 10, marked “dropped here while still borrowed”), while parser is used after it (line 11, “borrow later used here”). See Structs that hold references and How long a borrow lasts.

Question 6: a trait you cannot put in a box

Section titled “Question 6: a trait you cannot put in a box”
Rustsrc/bin/q6.rs
trait Shape {
fn area(&self) -> f64;
fn scaled(&self, by: f64) -> Self;
}
struct Square(f64);
impl Shape for Square {
fn area(&self) -> f64 {
self.0 * self.0
}
fn scaled(&self, by: f64) -> Self {
Square(self.0 * by)
}
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square(2.0))];
println!("{}", shapes[0].area());
}

Does it compile? If not, which error code, and which method is to blame?

Answer
$ cargo run --bin q6
error[E0038]: the trait `Shape` is not dyn compatible
  --> src/bin/q6.rs:18:29
   |
18 |     let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square(2.0))];
   |                             ^^^^^ `Shape` is not dyn compatible
   |
note: for a trait to be dyn compatible it needs to allow building a vtable
      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
  --> src/bin/q6.rs:3:34
   |
 1 | trait Shape {
   |       ----- this trait is not dyn compatible...
 2 |     fn area(&self) -> f64;
 3 |     fn scaled(&self, by: f64) -> Self;
   |                                  ^^^^ ...because method `scaled` references the `Self` type in its return type
   = help: consider moving `scaled` to another trait
   = help: only type `Square` implements `Shape`; consider using it directly instead.

A dyn Shape forgets the concrete type, but scaled returns Self, and there is no way to say what Self is once the type is forgotten. The compiler’s word for this is that the trait is not dyn compatible, and it names the method. See Which traits can be dyn.

Question 7: building through a private door

Section titled “Question 7: building through a private door”
Rustsrc/bin/q7.rs
mod bank {
pub struct Account {
balance: i64,
}
impl Account {
pub fn new() -> Account {
Account { balance: 0 }
}
pub fn balance(&self) -> i64 {
self.balance
}
}
}
fn main() {
let rich = bank::Account { balance: 1_000_000 };
let fresh = bank::Account::new();
println!("{}", rich.balance());
println!("{}", fresh.balance());
}

Does it compile? If not, which error code, and which line?

Answer
$ cargo run --bin q7
error[E0451]: field `balance` of struct `Account` is private
  --> src/bin/q7.rs:18:32
   |
18 |     let rich = bank::Account { balance: 1_000_000 };
   |                                ^^^^^^^ private field

The struct is pub, but its field is private, so code outside bank cannot name the field, and a struct literal has to name every field. Account::new() is the way in that the module chose to offer. See Private by default and Fields and variants.

Rustsrc/bin/q8.rs
fn biggest<T: PartialOrd>(items: &[T]) -> T {
let mut best = items[0];
for item in items {
if *item > best {
best = *item;
}
}
best
}
fn main() {
let words = [String::from("pear"), String::from("zebra")];
println!("{}", biggest(&words));
}

This does not compile. How many errors does it give, and which codes? Then change the function’s signature and body so that it prints zebra (there is more than one way).

Answer
$ cargo run --bin q8
error[E0508]: cannot move out of type `[T]`, a non-copy slice
 --> src/bin/q8.rs:2:20
  |
2 |     let mut best = items[0];
  |                    ^^^^^^^^
  |                    |
  |                    cannot move out of here
  |                    move occurs because `items[_]` has type `T`, which does not implement the `Copy` trait
  |
help: if `T` implemented `Clone`, you could clone the value
 --> src/bin/q8.rs:1:12
  |
1 | fn biggest<T: PartialOrd>(items: &[T]) -> T {
  |            ^ consider constraining this type parameter with `Clone`
2 |     let mut best = items[0];
  |                    -------- you could clone this value
help: consider borrowing here
  |
2 |     let mut best = &items[0];
  |                    +

error[E0507]: cannot move out of `*item` which is behind a shared reference
 --> src/bin/q8.rs:5:20
  |
5 |             best = *item;
  |                    ^^^^^ move occurs because `*item` has type `T`, which does not implement the `Copy` trait
  |
help: if `T` implemented `Clone`, you could clone the value
 --> src/bin/q8.rs:1:12
  |
1 | fn biggest<T: PartialOrd>(items: &[T]) -> T {
  |            ^ consider constraining this type parameter with `Clone`
...
5 |             best = *item;
  |                    ----- you could clone this value

Some errors have detailed explanations: E0507, E0508.

T is any type with PartialOrd, and that does not include Copy, so items[0] and *item try to move a value out of a slice that still owns it. Return a reference to the best item instead of a copy of it, so nothing is copied:

Rustsrc/bin/q8_fixed.rs
fn biggest<T: PartialOrd>(items: &[T]) -> &T {
let mut best = &items[0];
for item in items {
if *item > *best {
best = item;
}
}
best
}
fn main() {
let words = [String::from("pear"), String::from("zebra")];
println!("{}", biggest(&words));
}
$ cargo run --bin q8_fixed
zebra

A second fix is a Clone bound with .clone(), which copies the winning String:

Rustsrc/bin/q8_clone.rs
fn biggest<T: PartialOrd + Clone>(items: &[T]) -> T {
let mut best = items[0].clone();
for item in items {
if *item > best {
best = item.clone();
}
}
best
}
fn main() {
let words = [String::from("pear"), String::from("zebra")];
println!("{}", biggest(&words));
}
$ cargo run --bin q8_clone
zebra

A Copy bound is not a fix for this call: the body of biggest compiles, but String is not Copy, so the call in main is refused:

Rustsrc/bin/q8_copy.rs
fn biggest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for item in items {
if *item > best {
best = *item;
}
}
best
}
fn main() {
let words = [String::from("pear"), String::from("zebra")];
println!("{}", biggest(&words));
}
$ cargo run --bin q8_copy
error[E0277]: the trait bound `String: Copy` is not satisfied
  --> src/bin/q8_copy.rs:13:28
   |
13 |     println!("{}", biggest(&words));
   |                    ------- ^^^^^^ the trait `Copy` is not implemented for `String`
   |                    |
   |                    required by a bound introduced by this call
   |
note: required by a bound in `biggest`
  --> src/bin/q8_copy.rs:1:28
   |
 1 | fn biggest<T: PartialOrd + Copy>(items: &[T]) -> T {
   |                            ^^^^ required by this bound in `biggest`

See Bounds: what T may do and Owning the items.

The next checkpoint covers smart pointers and the borrow checker.