Skip to content
Learn Rust
Theme

Checkpoint: Data

This checkpoint is for the Data part of the course (structs, methods, enums, Option, errors, strings, vectors, maps, patterns) 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. A wrong guess you wrote down teaches you more than a right answer you only nodded at.

Rustsrc/bin/q1.rs
fn main() {
let word = String::from("naïve café");
println!("{} {}", word.len(), word.chars().count());
println!("{:?}", word.find('v'));
println!("{:?}", word.find("é"));
println!("{}", &word[1..4]);
}

Does it compile? What does it print, line by line?

Answer
terminal
$ cargo run --bin q1
12 10
Some(4)
Some(10)
aï

len() counts bytes, and ï and é take two bytes each in UTF-8, so the string is 12 bytes and 10 chars. find and the range [1..4] also use byte positions: bytes 1 to 3 are a and the two bytes of ï. A range that ended inside ï would panic instead; the Strings lesson runs that case. See Characters, bytes and what a person sees.

Rustsrc/bin/q2.rs
struct User {
name: String,
age: u32,
}
fn main() {
let a = User { name: String::from("Ada"), age: 36 };
let b = User { age: 37, ..a };
println!("{} {}", b.name, b.age);
println!("{}", a.age);
}

Does it compile? What does it print?

Answer
terminal
$ cargo run --bin q2
Ada 37
36

..a supplies only the fields the new struct does not list, so only name moves out of a. age is a u32 that b set itself, and a.age is a copy that is still there; only the whole of a and a.name are off limits. See Building one struct from another.

Rustsrc/bin/q3.rs
use std::collections::BTreeMap;
fn main() {
let mut stock: BTreeMap<&str, u32> = BTreeMap::new();
println!("{:?}", stock.insert("pear", 4));
println!("{:?}", stock.insert("pear", 7));
*stock.entry("fig").or_insert(10) += 1;
*stock.entry("fig").or_insert(10) += 1;
stock.entry("apple").or_default();
println!("{:?}", stock);
}

What are the three lines of output?

Answer
terminal
$ cargo run --bin q3
None
Some(4)
{"apple": 0, "fig": 12, "pear": 7}

insert returns the value it replaced as an Option, so the first call gives None and the second gives Some(4). entry(...).or_insert(10) stores 10 only when the key is missing and returns a &mut to the value, so two += 1 give 12. or_default() stores 0, and a BTreeMap prints in key order. See Creating, reading, changing and the entry API.

Rustsrc/bin/q4.rs
fn main() {
let nickname = Some(String::from("Ada"));
if let Some(n) = nickname {
println!("hello, {n}");
}
println!("{:?}", nickname);
}

Does it compile? If not, which error code, and what does the message say happened to nickname?

Answer
$ cargo run --bin q4
error[E0382]: borrow of partially moved value: `nickname`
 --> src/bin/q4.rs:6:22
  |
3 |     if let Some(n) = nickname {
  |                 - value partially moved here
...
6 |     println!("{:?}", nickname);
  |                      ^^^^^^^^ value borrowed here after partial move
  |
  = note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
  |
3 |     if let Some(ref n) = nickname {
  |                 +++

The pattern Some(n) takes the String out of nickname by value, so the Option is only partly there afterwards. The message says “borrow of partially moved value”: only the String inside Some moved, and that is the word to notice. Matching a reference (&nickname or nickname.as_ref()) borrows instead. See Does a pattern move, copy or borrow?.

Rustsrc/bin/q5.rs
fn describe(id: Option<u32>, admin: bool) -> &'static str {
match (id, admin) {
(Some(_), true) => "admin",
(Some(_), false) => "user",
(None, true) => "root",
}
}
fn main() {
println!("{}", describe(Some(7), false));
}

Does it compile? Which pattern does the compiler say is missing?

Answer
$ cargo run --bin q5
error[E0004]: non-exhaustive patterns: `(None, false)` not covered
 --> src/bin/q5.rs:2:11
  |
2 |     match (id, admin) {
  |           ^^^^^^^^^^^ pattern `(None, false)` not covered
  |
  = note: the matched value is of type `(Option<u32>, bool)`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
  |
5 ~         (None, true) => "root",
6 ~         (None, false) => todo!(),

A match has to cover every combination of the tuple, and the three arms leave out (None, false). The error names the case so you do not have to work it out. See Several values at once: tuples and Every case must be covered.

Rustsrc/bin/q6.rs
fn main() {
let found: Option<i32> = Some(41);
let next = found + 1;
println!("{next}");
}

Does it compile? Which error code?

Answer
$ cargo run --bin q6
error[E0369]: cannot add `{integer}` to `Option<i32>`
 --> src/bin/q6.rs:3:22
  |
3 |     let next = found + 1;
  |                ----- ^ - {integer}
  |                |
  |                Option<i32>
  ... (notes pointing into the standard library trimmed)

Option<i32> and i32 are different types, and Option has no +. Open it first with match, if let or a method such as map. See You can’t use an Option as the value.

Rustsrc/bin/q7.rs
fn main() {
let n: i32 = "21".parse()?;
println!("{}", n * 2);
}

This does not compile. What is the error code? Keep the ? and do not use unwrap or expect: what change to main makes it compile, so that a bad number is reported as an error and not as a panic?

Answer
$ cargo run --bin q7
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
 --> src/bin/q7.rs:2:30
  |
1 | fn main() {
  | --------- this function should return `Result` or `Option` to accept `?`
2 |     let n: i32 = "21".parse()?;
  |                              ^ cannot use the `?` operator in a function that returns `()`
  |
help: consider adding return type
  |
1 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
2 |     let n: i32 = "21".parse()?;
3 |     println!("{}", n * 2);
4 +     Ok(())

? returns the error to the caller, and main returns (), which cannot hold one. Give main a Result return type and finish with Ok(()). (.unwrap() in place of ? would also compile, but a bad number would then panic.) The fixed file below also parses "2l" on purpose, to show what happens when an Err reaches main:

Rustsrc/bin/q7_fixed.rs
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let n: i32 = "21".parse()?;
println!("{}", n * 2);
let m: i32 = "2l".parse()?;
println!("{}", m * 2);
Ok(())
}
$ cargo run --bin q7_fixed
42
Error: ParseIntError { kind: InvalidDigit }

The 42 goes to standard output. When main returns an Err, Rust prints Error: and the error’s Debug form to standard error, and the program ends with exit status 1 (the session shows it). See ? needs a function that returns a Result and main can return a Result.

Rustsrc/bin/q8.rs
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
scores.push(40);
println!("{first} of {}", scores.len());
}

This does not compile. Which error code? Change the program so that it prints 10 of 4.

Answer
$ cargo run --bin q8
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/bin/q8.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(40);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first} of {}", scores.len());
  |                ----- immutable borrow later used here

first is a reference into the vector, and push may move the vector’s items to a new place, so the compiler refuses the reference to still be alive. One fix copies the number out instead of keeping a reference (moving the push above the let first line would also work, because then nothing borrows the vector when it grows):

Rustsrc/bin/q8_fixed.rs
fn main() {
let mut scores = vec![10, 20, 30];
let first = scores[0];
scores.push(40);
println!("{first} of {}", scores.len());
}
$ cargo run --bin q8_fixed
10 of 4

See Changing a list you are reading.

The next checkpoint covers collections, closures and iterators.