1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-28 19:41:32 +02:00

Publish Comprehensive Rust 🦀

This commit is contained in:
Martin Geisler
2022-12-21 16:36:30 +01:00
commit c212a473ba
252 changed files with 8047 additions and 0 deletions

View File

@ -0,0 +1,7 @@
# Day 1: Afternoon Exercises
We will look at two things:
* A small book library,
* Iterators and ownership (hard).

View File

@ -0,0 +1,42 @@
# Designing a Library
We will learn much more about structs and the `Vec<T>` type tomorrow. For now,
you just need to know part of its API:
```rust,editable
fn main() {
let mut vec = vec![10, 20];
vec.push(30);
println!("middle value: {}", vec[vec.len() / 2]);
for item in vec.iter() {
println!("item: {item}");
}
}
```
Use this to create a library application. Copy the code below to
<https://play.rust-lang.org/> and update the types to make it compile:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include book-library.rs:setup}}
{{#include book-library.rs:Library_new}}
unimplemented!()
}
{{#include book-library.rs:Library_len}}
{{#include book-library.rs:Library_is_empty}}
{{#include book-library.rs:Library_add_book}}
{{#include book-library.rs:Library_print_books}}
{{#include book-library.rs:Library_oldest_book}}
}
{{#include book-library.rs:main}}
```

View File

@ -0,0 +1,168 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: setup
struct Library {
books: Vec<Book>,
}
struct Book {
title: String,
year: u16,
}
impl Book {
// This is a constructor, used below.
fn new(title: &str, year: u16) -> Book {
Book {
title: String::from(title),
year,
}
}
}
// This makes it possible to print Book values with {}.
impl std::fmt::Display for Book {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})", self.title, self.year)
}
}
// ANCHOR_END: setup
// ANCHOR: Library_new
impl Library {
fn new() -> Library {
// ANCHOR_END: Library_new
Library { books: Vec::new() }
}
// ANCHOR: Library_len
//fn len(self) -> usize {
// unimplemented!()
//}
// ANCHOR_END: Library_len
fn len(&self) -> usize {
self.books.len()
}
// ANCHOR: Library_is_empty
//fn is_empty(self) -> bool {
// unimplemented!()
//}
// ANCHOR_END: Library_is_empty
fn is_empty(&self) -> bool {
self.books.is_empty()
}
// ANCHOR: Library_add_book
//fn add_book(self, book: Book) {
// unimplemented!()
//}
// ANCHOR_END: Library_add_book
fn add_book(&mut self, book: Book) {
self.books.push(book)
}
// ANCHOR: Library_print_books
//fn print_books(self) {
// unimplemented!()
//}
// ANCHOR_END: Library_print_books
fn print_books(&self) {
for book in &self.books {
println!("{}", book);
}
}
// ANCHOR: Library_oldest_book
//fn oldest_book(self) -> Option<&Book> {
// unimplemented!()
//}
// ANCHOR_END: Library_oldest_book
fn oldest_book(&self) -> Option<&Book> {
self.books.iter().min_by_key(|book| book.year)
}
}
// ANCHOR: main
fn main() {
// This shows the desired behavior. Uncomment the code below and
// implement the missing methods. You will need to update the
// method signatures, including the "self" parameter!
let library = Library::new();
//println!("Our library is empty: {}", library.is_empty());
//
//library.add_book(Book::new("Lord of the Rings", 1954));
//library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
//
//library.print_books();
//
//match library.oldest_book() {
// Some(book) => println!("My oldest book is {book}"),
// None => println!("My library is empty!"),
//}
//
//println!("Our library has {} books", library.len());
}
// ANCHOR_END: main
#[test]
fn test_library_len() {
let mut library = Library::new();
assert_eq!(library.len(), 0);
assert!(library.is_empty());
library.add_book(Book::new("Lord of the Rings", 1954));
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
assert_eq!(library.len(), 2);
assert!(!library.is_empty());
}
#[test]
fn test_library_is_empty() {
let mut library = Library::new();
assert!(library.is_empty());
library.add_book(Book::new("Lord of the Rings", 1954));
assert!(!library.is_empty());
}
#[test]
fn test_library_print_books() {
let mut library = Library::new();
library.add_book(Book::new("Lord of the Rings", 1954));
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
// We could try and capture stdout, but let us just call the
// method to start with.
library.print_books();
}
#[test]
fn test_library_oldest_book() {
let mut library = Library::new();
assert!(library.oldest_book().is_none());
library.add_book(Book::new("Lord of the Rings", 1954));
assert_eq!(
library.oldest_book().map(|b| b.title.as_str()),
Some("Lord of the Rings")
);
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
assert_eq!(
library.oldest_book().map(|b| b.title.as_str()),
Some("Alice's Adventures in Wonderland")
);
}

View File

@ -0,0 +1,75 @@
# Arrays and `for` Loops
We saw that an array can be declared like this:
```rust
let array = [10, 20, 30];
```
You can print such an array by asking for its debug representation with `{:?}`:
```rust,editable
fn main() {
let array = [10, 20, 30];
println!("array: {array:?}");
}
```
Rust lets you iterate over things like arrays and ranges using the `for`
keyword:
```rust,editable
fn main() {
let array = [10, 20, 30];
print!("Iterating over array:");
for n in array {
print!(" {n}");
}
println!();
print!("Iterating over range:");
for i in 0..3 {
print!(" {}", array[i]);
}
println!();
}
```
Use the above to write a function `pretty_print` which pretty-print a matrix and
a function `transpose` which will transpose a matrix (turn rows into columns):
```bob
⎛⎡1 2 3⎤⎞ ⎡1 4 7⎤
"transpose"⎜⎢4 5 6⎥⎟ "=="⎢2 5 8⎥
⎝⎣7 8 9⎦⎠ ⎣3 6 9⎦
```
Hard-code both functions to operate on 3 × 3 matrices.
Copy the code below to <https://play.rust-lang.org/> and implement the
functions:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include for-loops.rs:transpose}}
unimplemented!()
}
{{#include for-loops.rs:pretty_print}}
unimplemented!()
}
{{#include for-loops.rs:main}}
```
## Bonus Question
Could you use `&[i32]` slices instead of hard-coded 3 × 3 matrices for your
argument and return types? Something like `&[&[i32]]` for a two-dimensional
slice-of-slices. Why or why not?
See the [`ndarray` crate](https://docs.rs/ndarray/) for a production quality
implementation.

View File

@ -0,0 +1,69 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: transpose
fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
// ANCHOR_END: transpose
let mut result = [[0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
result[j][i] = matrix[i][j];
}
}
return result;
}
// ANCHOR: pretty_print
fn pretty_print(matrix: &[[i32; 3]; 3]) {
// ANCHOR_END: pretty_print
for row in matrix {
println!("{row:?}");
}
}
// ANCHOR: tests
#[test]
fn test_transpose() {
let matrix = [
[101, 102, 103], //
[201, 202, 203],
[301, 302, 303],
];
let transposed = transpose(matrix);
assert_eq!(
transposed,
[
[101, 201, 301], //
[102, 202, 302],
[103, 203, 303],
]
);
}
// ANCHOR_END: tests
// ANCHOR: main
fn main() {
let matrix = [
[101, 102, 103], // <-- the comment make rustfmt add a newline
[201, 202, 203],
[301, 302, 303],
];
println!("matrix:");
pretty_print(&matrix);
let transposed = transpose(matrix);
println!("transposed:");
pretty_print(&transposed);
}

View File

@ -0,0 +1,41 @@
# Implicit Conversions
Rust will not automatically apply _implicit conversions_ between types ([unlike
C++][3]). You can see this in a program like this:
```rust,editable,compile_fail
fn multiply(x: i16, y: i16) -> i16 {
x * y
}
fn main() {
let x: i8 = 15;
let y: i16 = 1000;
println!("{x} * {y} = {}", multiply(x, y));
}
```
The Rust integer types all implement the [`From<T>`][1] and [`Into<T>`][2]
traits to let us convert between them. The `From<T>` trait has a single `from()`
method and similarly, the `Into<T>` trait has a single `into()` method.
Implementing these traits is how a type expresses that it can be converted into
another type.
The standard library has an implementation of `From<i8> for i16`, which means
that we can convert an `i8` to an `i16` by calling the `into()` method on the
`i8`.
1. Execute the above program and look at the compiler error.
2. Update the code above to use `into()` to do the conversion.
3. Change the types of `x` and `y` to other things (such as `f32`, `bool`,
`i128`) to see which types you can convert to which other types. Try
converting small types to big types and the other way around. Check the
[standard library documentation][1] to see if `From<T>` is implemented for
the pairs you check.
[1]: https://doc.rust-lang.org/std/convert/trait.From.html
[2]: https://doc.rust-lang.org/std/convert/trait.Into.html
[3]: https://en.cppreference.com/w/cpp/language/implicit_conversion

View File

@ -0,0 +1,110 @@
# Iterators and Ownership
The ownership model of Rust affects many APIs. An example of this is the
[`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) and
[`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html)
traits.
## `Iterator`
Traits are like interfaces: they describe behavior (methods) for a type. The
`Iterator` trait simply says that you can call `next` until you get `None` back:
```rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
```
You use this trait like this:
```rust,editable
fn main() {
let v: Vec<i8> = vec![10, 20, 30];
let mut iter = v.iter();
println!("v[0]: {:?}", iter.next());
println!("v[1]: {:?}", iter.next());
println!("v[2]: {:?}", iter.next());
println!("No more items: {:?}", iter.next());
}
```
What is the type returned by the iterator? Test your answer here:
```rust,editable,compile_fail
fn main() {
let v: Vec<i8> = vec![10, 20, 30];
let mut iter = v.iter();
let v0: Option<..> = iter.next();
println!("v0: {v0:?}");
}
```
Why is this type used?
## `IntoIterator`
The `Iterator` trait tells you how to _iterate_ once you have created an
iterator. The related trait `IntoIterator` tells you how to create the iterator:
```rust
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
```
The syntax here means that every implementation of `IntoIterator` must
declare two types:
* `Item`: the type we iterate over, such as `i8`,
* `IntoIter`: the `Iterator` type returned by the `into_iter` method.
Note that `IntoIter` and `Item` are linked: the iterator must have the same
`Item` type, which means that it returns `Option<Item>`
Like before, what is the type returned by the iterator?
```rust,editable,compile_fail
fn main() {
let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
let mut iter = v.into_iter();
let v0: Option<..> = iter.next();
println!("v0: {v0:?}");
}
```
## `for` Loops
Now that we know both `Iterator` and `IntoIterator`, we can build `for` loops.
They call `into_iter()` on an expression and iterates over the resulting
iterator:
```rust,editable
fn main() {
let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
for word in &v {
println!("word: {word}");
}
for word in v {
println!("word: {word}");
}
}
```
What is the type of `word` in each loop?
Experiment with the code above and then consult the documentation for [`impl
IntoIterator for
&Vec<T>`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-2)
and [`impl IntoIterator for
Vec<T>`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-1)
to check your answers.

View File

@ -0,0 +1,7 @@
# Day 1: Morning Exercises
In these exercises, we will explore two parts of Rust:
* Implicit conversions between types.
* Arrays and `for` loops.

View File

@ -0,0 +1,9 @@
# Day 1 Afternoon Exercises
## Designing a Library
([back to exercise](book-library.md))
```rust
{{#include book-library.rs}}
```

View File

@ -0,0 +1,9 @@
# Day 1 Morning Exercises
## Arrays and `for` Loops
([back to exercise](for-loops.md))
```rust
{{#include for-loops.rs}}
```

View File

@ -0,0 +1,3 @@
# Day 2: Afternoon Exercises
The exercises for this afternoon will focus on strings and iterators.

View File

@ -0,0 +1,32 @@
# Health Statistics
{{#include ../../../third_party/rust-on-exercism/health-statistics.md}}
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
methods:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs}}
fn main() {
let bob = User::new(String::from("Bob"), 32, 155.2);
println!("I'm {} and my age is {}", bob.name(), bob.age());
}
#[test]
fn test_weight() {
let bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.weight(), 155.2);
}
#[test]
fn test_set_age() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.age(), 32);
bob.set_age(33);
assert_eq!(bob.age(), 33);
}
```

View File

@ -0,0 +1,35 @@
# Luhn Algorithm
The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is used to
validate credit card numbers. The algorithm takes a string as input and does the
following to validate the credit card number:
* Ignore all spaces. Reject number with less than two digits.
* Moving from right to left, double every second digit: for the number `1234`,
we double `3` and `1`.
* After doubling a digit, sum the digits. So doubling `7` becomes `14` which
becomes `5`.
* Sum all the undoubled and doubled digits.
* The credit card number is valid if the sum is ends with `0`.
Copy the following code to <https://play.rust-lang.org/> and implement the
function:
```rust
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include luhn.rs:luhn}}
unimplemented!()
}
{{#include luhn.rs:unit-tests}}
#[allow(dead_code)]
fn main() {}
```

View File

@ -0,0 +1,88 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: luhn
pub fn luhn(cc_number: &str) -> bool {
// ANCHOR_END: luhn
let mut digits_seen = 0;
let mut sum = 0;
for (i, ch) in cc_number.chars().rev().filter(|&ch| ch != ' ').enumerate() {
match ch.to_digit(10) {
Some(d) => {
sum += if i % 2 == 1 {
let dd = d * 2;
dd / 10 + dd % 10
} else {
d
};
digits_seen += 1;
}
None => return false,
}
}
if digits_seen < 2 {
return false;
}
sum % 10 == 0
}
fn main() {
let cc_number = "1234 5678 1234 5670";
println!(
"Is {} a valid credit card number? {}",
cc_number,
if luhn(cc_number) { "yes" } else { "no" }
);
}
// ANCHOR: unit-tests
#[test]
fn test_non_digit_cc_number() {
assert!(!luhn("foo"));
}
#[test]
fn test_empty_cc_number() {
assert!(!luhn(""));
assert!(!luhn(" "));
assert!(!luhn(" "));
assert!(!luhn(" "));
}
#[test]
fn test_single_digit_cc_number() {
assert!(!luhn("0"));
}
#[test]
fn test_two_digit_cc_number() {
assert!(luhn(" 0 0 "));
}
#[test]
fn test_valid_cc_number() {
assert!(luhn("4263 9826 4026 9299"));
assert!(luhn("4539 3195 0343 6467"));
assert!(luhn("7992 7398 713"));
}
#[test]
fn test_invalid_cc_number() {
assert!(!luhn("4223 9826 4026 9299"));
assert!(!luhn("4539 3195 0343 6476"));
assert!(!luhn("8273 1232 7352 0569"));
}
// ANCHOR_END: unit-tests

View File

@ -0,0 +1,7 @@
# Day 2: Morning Exercises
We will look at implementing methods in two contexts:
* Simple struct which tracks health statistics.
* Multiple structs and enums for a drawing library.

View File

@ -0,0 +1,41 @@
# Polygon Struct
We will create a `Polygon` struct which contain some points. Copy the code below
to <https://play.rust-lang.org/> and fill in the missing methods to make the
tests pass:
```rust
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include points-polygons.rs:Point}}
// add fields
}
{{#include points-polygons.rs:Point-impl}}
// add methods
}
{{#include points-polygons.rs:Polygon}}
// add fields
}
{{#include points-polygons.rs:Polygon-impl}}
// add methods
}
{{#include points-polygons.rs:Circle}}
// add fields
}
{{#include points-polygons.rs:Circle-impl}}
// add methods
}
{{#include points-polygons.rs:Shape}}
{{#include points-polygons.rs:unit-tests}}
#[allow(dead_code)]
fn main() {}
```

View File

@ -0,0 +1,223 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
// ANCHOR: Point
pub struct Point {
// ANCHOR_END: Point
x: i32,
y: i32,
}
// ANCHOR: Point-impl
impl Point {
// ANCHOR_END: Point-impl
pub fn new(x: i32, y: i32) -> Point {
Point { x, y }
}
pub fn magnitude(self) -> f64 {
f64::from(self.x.pow(2) + self.y.pow(2)).sqrt()
}
pub fn dist(self, other: Point) -> f64 {
(self - other).magnitude()
}
}
impl std::ops::Add for Point {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
impl std::ops::Sub for Point {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
// ANCHOR: Polygon
pub struct Polygon {
// ANCHOR_END: Polygon
points: Vec<Point>,
}
// ANCHOR: Polygon-impl
impl Polygon {
// ANCHOR_END: Polygon-impl
pub fn new() -> Polygon {
Polygon { points: Vec::new() }
}
pub fn add_point(&mut self, point: Point) {
self.points.push(point);
}
pub fn left_most_point(&self) -> Option<Point> {
self.points.iter().min_by_key(|p| p.x).copied()
}
pub fn iter(&self) -> impl Iterator<Item = &Point> {
self.points.iter()
}
pub fn length(&self) -> f64 {
if self.points.is_empty() {
return 0.0;
}
let mut result = 0.0;
let mut last_point = self.points[0];
for point in &self.points[1..] {
result += last_point.dist(*point);
last_point = *point;
}
result += last_point.dist(self.points[0]);
result
}
}
// ANCHOR: Circle
pub struct Circle {
// ANCHOR_END: Circle
center: Point,
radius: i32,
}
// ANCHOR: Circle-impl
impl Circle {
// ANCHOR_END: Circle-impl
pub fn new(center: Point, radius: i32) -> Circle {
Circle { center, radius }
}
pub fn circumference(&self) -> f64 {
2.0 * std::f64::consts::PI * f64::from(self.radius)
}
pub fn dist(&self, other: &Self) -> f64 {
self.center.dist(other.center)
}
}
// ANCHOR: Shape
pub enum Shape {
Polygon(Polygon),
Circle(Circle),
}
// ANCHOR_END: Shape
impl From<Polygon> for Shape {
fn from(poly: Polygon) -> Self {
Shape::Polygon(poly)
}
}
impl From<Circle> for Shape {
fn from(circle: Circle) -> Self {
Shape::Circle(circle)
}
}
impl Shape {
pub fn circumference(&self) -> f64 {
match self {
Shape::Polygon(poly) => poly.length(),
Shape::Circle(circle) => circle.circumference(),
}
}
}
// ANCHOR: unit-tests
#[cfg(test)]
mod tests {
use super::*;
fn round_two_digits(x: f64) -> f64 {
(x * 100.0).round() / 100.0
}
#[test]
fn test_point_magnitude() {
let p1 = Point::new(12, 13);
assert_eq!(round_two_digits(p1.magnitude()), 17.69);
}
#[test]
fn test_point_dist() {
let p1 = Point::new(10, 10);
let p2 = Point::new(14, 13);
assert_eq!(round_two_digits(p1.dist(p2)), 5.00);
}
#[test]
fn test_point_add() {
let p1 = Point::new(16, 16);
let p2 = p1 + Point::new(-4, 3);
assert_eq!(p2, Point::new(12, 19));
}
#[test]
fn test_polygon_left_most_point() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);
let mut poly = Polygon::new();
poly.add_point(p1);
poly.add_point(p2);
assert_eq!(poly.left_most_point(), Some(p1));
}
#[test]
fn test_polygon_iter() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);
let mut poly = Polygon::new();
poly.add_point(p1);
poly.add_point(p2);
let points = poly.iter().cloned().collect::<Vec<_>>();
assert_eq!(points, vec![Point::new(12, 13), Point::new(16, 16)]);
}
#[test]
fn test_shape_circumferences() {
let mut poly = Polygon::new();
poly.add_point(Point::new(12, 13));
poly.add_point(Point::new(16, 16));
let shapes = vec![
Shape::from(poly),
Shape::from(Circle::new(Point::new(10, 20), 5)),
];
let circumferences = shapes
.iter()
.map(Shape::circumference)
.map(round_two_digits)
.collect::<Vec<_>>();
assert_eq!(circumferences, vec![10.0, 31.42]);
}
}
// ANCHOR_END: unit-tests

View File

@ -0,0 +1,17 @@
# Day 2 Afternoon Exercises
## Luhn Algorithm
([back to exercise](luhn.md))
```rust
{{#include luhn.rs}}
```
## Strings and Iterators
([back to exercise](strings-iterators.md))
```rust
{{#include strings-iterators.rs}}
```

View File

@ -0,0 +1,12 @@
# Day 2 Morning Exercises
## Points and Polygons
([back to exercise](points-polygons.md))
```rust
{{#include points-polygons.rs}}
#[allow(dead_code)]
fn main() {}
```

View File

@ -0,0 +1,21 @@
# Strings and Iterators
In this exercise, you are implementing a routing component of a web server. The
server is configured with a number of _path prefixes_ which are matched against
_request paths_. The path prefixes can contain a wildcard character which
matches a full segment. See the unit tests below.
Copy the following code to <https://play.rust-lang.org/> and make the tests
pass. Try avoiding allocating a `Vec` for your intermediate results:
```rust
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include strings-iterators.rs:prefix_matches}}
unimplemented!()
}
{{#include strings-iterators.rs:unit-tests}}
```

View File

@ -0,0 +1,75 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: prefix_matches
pub fn prefix_matches(prefix: &str, request_path: &str) -> bool {
// ANCHOR_END: prefix_matches
let mut prefixes = prefix
.split('/')
.map(|p| Some(p))
.chain(std::iter::once(None));
let mut request_paths = request_path
.split('/')
.map(|p| Some(p))
.chain(std::iter::once(None));
for (prefix, request_path) in prefixes.by_ref().zip(&mut request_paths) {
match (prefix, request_path) {
(Some(prefix), Some(request_path)) => {
if (prefix != "*") && (prefix != request_path) {
return false;
}
}
(Some(_), None) => return false,
(None, None) => break,
(None, Some(_)) => break,
}
}
true
}
// ANCHOR: unit-tests
#[test]
fn test_matches_without_wildcard() {
assert!(prefix_matches("/v1/publishers", "/v1/publishers"));
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc-123"));
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc/books"));
assert!(!prefix_matches("/v1/publishers", "/v1"));
assert!(!prefix_matches("/v1/publishers", "/v1/publishersBooks"));
assert!(!prefix_matches("/v1/publishers", "/v1/parent/publishers"));
}
#[test]
fn test_matches_with_wildcard() {
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/books"
));
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/bar/books"
));
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/books/book1"
));
assert!(!prefix_matches("/v1/publishers/*/books", "/v1/publishers"));
assert!(!prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/booksByAuthor"
));
}
// ANCHOR_END: unit-tests

View File

@ -0,0 +1,3 @@
# Day 3: Afternoon Exercises
Let us build a safe wrapper for reading directory content!

View File

@ -0,0 +1,3 @@
# Day 3: Morning Exercises
We will design a classical GUI library traits and trait objects.

View File

@ -0,0 +1,47 @@
# Safe FFI Wrapper
Rust has great support for calling functions through a _foreign function
interface_ (FFI). We will use this to build a safe wrapper the `glibc` functions
you would use from C to read the filenames of a directory.
You will want to consult the manual pages:
* [`opendir(3)`](https://man7.org/linux/man-pages/man3/opendir.3.html)
* [`readdir(3)`](https://man7.org/linux/man-pages/man3/readdir.3.html)
* [`closedir(3)`](https://man7.org/linux/man-pages/man3/closedir.3.html)
You will also want to browse the [`std::ffi`] module, particular for [`CStr`]
and [`CString`] types which are used to hold NUL-terminated strings coming from
C. The [Nomicon] also has a very useful chapter about FFI.
[`std::ffi`]: https://doc.rust-lang.org/std/ffi/
[`CStr`]: https://doc.rust-lang.org/std/ffi/struct.CStr.html
[`CString`]: https://doc.rust-lang.org/std/ffi/struct.CString.html
[Nomicon]: https://doc.rust-lang.org/nomicon/ffi.html
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
functions and methods:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_imports, unused_variables, dead_code)]
{{#include safe-ffi-wrapper.rs:ffi}}
{{#include safe-ffi-wrapper.rs:DirectoryIterator}}
unimplemented!()
}
}
{{#include safe-ffi-wrapper.rs:Iterator}}
unimplemented!()
}
}
{{#include safe-ffi-wrapper.rs:Drop}}
unimplemented!()
}
}
{{#include safe-ffi-wrapper.rs:main}}
```

View File

@ -0,0 +1,110 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: ffi
mod ffi {
use std::os::raw::{c_char, c_int, c_long, c_ulong, c_ushort};
// Opaque type. See https://doc.rust-lang.org/nomicon/ffi.html.
#[repr(C)]
pub struct DIR {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
// Layout as per readdir(3) and definitions in /usr/include/x86_64-linux-gnu.
#[repr(C)]
pub struct dirent {
pub d_ino: c_long,
pub d_off: c_ulong,
pub d_reclen: c_ushort,
pub d_type: c_char,
pub d_name: [c_char; 256],
}
extern "C" {
pub fn opendir(s: *const c_char) -> *mut DIR;
pub fn readdir(s: *mut DIR) -> *const dirent;
pub fn closedir(s: *mut DIR) -> c_int;
}
}
use std::ffi::{CStr, CString, OsStr, OsString};
use std::os::unix::ffi::OsStrExt;
#[derive(Debug)]
struct DirectoryIterator {
path: CString,
dir: *mut ffi::DIR,
}
// ANCHOR_END: ffi
// ANCHOR: DirectoryIterator
impl DirectoryIterator {
fn new(path: &str) -> Result<DirectoryIterator, String> {
// Call opendir and return a Ok value if that worked,
// otherwise return Err with a message.
// ANCHOR_END: DirectoryIterator
let path = CString::new(path).map_err(|err| format!("Invalid path: {err}"))?;
// SAFETY: path.as_ptr() cannot be NULL.
let dir = unsafe { ffi::opendir(path.as_ptr()) };
if dir.is_null() {
Err(format!("Could not open {:?}", path))
} else {
Ok(DirectoryIterator { path, dir })
}
}
}
// ANCHOR: Iterator
impl Iterator for DirectoryIterator {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
// Keep calling readdir until we get a NULL pointer back.
// ANCHOR_END: Iterator
// SAFETY: self.dir is never NULL.
let dirent = unsafe { ffi::readdir(self.dir) };
if dirent.is_null() {
// We have reached the end of the directory.
return None;
}
// SAFETY: dirent is not NULL and dirent.d_name is NUL
// terminated.
let d_name = unsafe { CStr::from_ptr((*dirent).d_name.as_ptr()) };
let os_str = OsStr::from_bytes(d_name.to_bytes());
Some(os_str.to_owned())
}
}
// ANCHOR: Drop
impl Drop for DirectoryIterator {
fn drop(&mut self) {
// Call closedir as needed.
// ANCHOR_END: Drop
if !self.dir.is_null() {
// SAFETY: self.dir is not NULL.
if unsafe { ffi::closedir(self.dir) } != 0 {
panic!("Could not close {:?}", self.path);
}
}
}
}
// ANCHOR: main
fn main() -> Result<(), String> {
let iter = DirectoryIterator::new(".")?;
println!("files: {:#?}", iter.collect::<Vec<_>>());
Ok(())
}
// ANCHOR_END: main

View File

@ -0,0 +1,91 @@
# A Simple GUI Library
Let us design a classical GUI library using our new knowledge of traits and
trait objects.
We will have a number of widgets in our library:
* `Window`: has a `title` and contains other widgets.
* `Button`: has a `label` and a callback function which is invoked when the
button is pressed.
* `Label`: has a `label`.
The widgets will implement a `Widget` trait, see below.
Copy the code below to <https://play.rust-lang.org/>, fill in the missing
`draw_into` methods so that you implement the `Widget` trait:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_imports, unused_variables, dead_code)]
{{#include simple-gui.rs:setup}}
{{#include simple-gui.rs:Label-width}}
unimplemented!()
}
{{#include simple-gui.rs:Label-draw_into}}
unimplemented!()
}
}
{{#include simple-gui.rs:Button-width}}
unimplemented!()
}
{{#include simple-gui.rs:Button-draw_into}}
unimplemented!()
}
}
{{#include simple-gui.rs:Window-width}}
unimplemented!()
}
{{#include simple-gui.rs:Window-draw_into}}
unimplemented!()
}
}
{{#include simple-gui.rs:main}}
```
The output of the above program can be something simple like this:
```text
========
Rust GUI Demo 1.23
========
This is a small text GUI demo.
| Click me! |
```
If you want to draw aligned text, you can use the
[fill/alignment](https://doc.rust-lang.org/std/fmt/index.html#fillalignment)
formatting operators. In particular, notice how you can pad with different
characters (here a `'/'`) and how you can control alignment:
```rust,editable
fn main() {
let width = 10;
println!("left aligned: |{:/<width$}|", "foo");
println!("centered: |{:/^width$}|", "foo");
println!("right aligned: |{:/>width$}|", "foo");
}
```
Using such alignment tricks, you can for example produce output like this:
```text
+--------------------------------+
| Rust GUI Demo 1.23 |
+================================+
| This is a small text GUI demo. |
| +-----------+ |
| | Click me! | |
| +-----------+ |
+--------------------------------+
```

View File

@ -0,0 +1,160 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: setup
pub trait Widget {
/// Natural width of `self`.
fn width(&self) -> usize;
/// Draw the widget into a buffer.
fn draw_into(&self, buffer: &mut dyn std::fmt::Write);
/// Draw the widget on standard output.
fn draw(&self) {
let mut buffer = String::new();
self.draw_into(&mut buffer);
println!("{}", &buffer);
}
}
pub struct Label {
label: String,
}
impl Label {
fn new(label: &str) -> Label {
Label {
label: label.to_owned(),
}
}
}
pub struct Button {
label: Label,
callback: Box<dyn FnMut()>,
}
impl Button {
fn new(label: &str, callback: Box<dyn FnMut()>) -> Button {
Button {
label: Label::new(label),
callback,
}
}
}
pub struct Window {
title: String,
widgets: Vec<Box<dyn Widget>>,
}
impl Window {
fn new(title: &str) -> Window {
Window {
title: title.to_owned(),
widgets: Vec::new(),
}
}
fn add_widget(&mut self, widget: Box<dyn Widget>) {
self.widgets.push(widget);
}
}
// ANCHOR_END: setup
// ANCHOR: Window-width
impl Widget for Window {
fn width(&self) -> usize {
// ANCHOR_END: Window-width
std::cmp::max(
self.title.chars().count(),
self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
)
}
// ANCHOR: Window-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Window-draw_into
let mut inner = String::new();
for widget in &self.widgets {
widget.draw_into(&mut inner);
}
let window_width = self.width();
// TODO: after learning about error handling, you can change
// draw_into to return Result<(), std::fmt::Error>. Then use
// the ?-operator here instead of .unwrap().
writeln!(buffer, "+-{:-<window_width$}-+", "").unwrap();
writeln!(buffer, "| {:^window_width$} |", &self.title).unwrap();
writeln!(buffer, "+={:=<window_width$}=+", "").unwrap();
for line in inner.lines() {
writeln!(buffer, "| {:window_width$} |", line).unwrap();
}
writeln!(buffer, "+-{:-<window_width$}-+", "").unwrap();
}
}
// ANCHOR: Button-width
impl Widget for Button {
fn width(&self) -> usize {
// ANCHOR_END: Button-width
self.label.width() + 8 // add a bit of padding
}
// ANCHOR: Button-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Button-draw_into
let width = self.width();
let mut label = String::new();
self.label.draw_into(&mut label);
writeln!(buffer, "+{:-<width$}+", "").unwrap();
for line in label.lines() {
writeln!(buffer, "|{:^width$}|", &line).unwrap();
}
writeln!(buffer, "+{:-<width$}+", "").unwrap();
}
}
// ANCHOR: Label-width
impl Widget for Label {
fn width(&self) -> usize {
// ANCHOR_END: Label-width
self.label
.lines()
.map(|line| line.chars().count())
.max()
.unwrap_or(0)
}
// ANCHOR: Label-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Label-draw_into
writeln!(buffer, "{}", &self.label).unwrap();
}
}
// ANCHOR: main
fn main() {
let mut window = Window::new("Rust GUI Demo 1.23");
window.add_widget(Box::new(Label::new("This is a small text GUI demo.")));
window.add_widget(Box::new(Button::new(
"Click me!",
Box::new(|| println!("You clicked the button!")),
)));
window.draw();
}
// ANCHOR_END: main

View File

@ -0,0 +1,9 @@
# Day 3 Afternoon Exercises
## Safe FFI Wrapper
([back to exercise](safe-ffi-wrapper.md))
```rust
{{#include safe-ffi-wrapper.rs}}
```

View File

@ -0,0 +1,9 @@
# Day 3 Morning Exercise
## A Simple GUI Library
([back to exercise](simple-gui.md))
```rust
{{#include simple-gui.rs}}
```

View File

@ -0,0 +1,8 @@
# Exercises
For the last exercise, we will look at one of the projects you work with. Let us
group up and do this together. Some suggestions:
* Call your AIDL service with a client written in Rust.
* Move a function from your project to Rust and call it.

View File

@ -0,0 +1,37 @@
# Dining Philosophers
The dining philosophers problem is a classic problem in concurrency:
> Five philosophers dine together at the same table. Each philosopher has their
> own place at the table. There is a fork between each plate. The dish served is
> a kind of spaghetti which has to be eaten with two forks. Each philosopher can
> only alternately think and eat. Moreover, a philosopher can only eat their
> spaghetti when they have both a left and right fork. Thus two forks will only
> be available when their two nearest neighbors are thinking, not eating. After
> an individual philosopher finishes eating, they will put down both forks.
You will need a local [Cargo installation](../../cargo/running-locally.md) for
this exercise. Copy the code below to `src/main.rs` file, fill out the blanks,
and test that `cargo run` does not deadlock:
```rust,compile_fail
{{#include dining-philosophers.rs:Philosopher}}
// left_fork: ...
// right_fork: ...
// thoughts: ...
}
{{#include dining-philosophers.rs:Philosopher-think}}
{{#include dining-philosophers.rs:Philosopher-eat}}
// Pick up forks...
{{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks
// Create philosophers
// Make them think and eat
// Output their thoughts
}
```

View File

@ -0,0 +1,95 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: Philosopher
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct Fork;
struct Philosopher {
name: String,
// ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>,
right_fork: Arc<Mutex<Fork>>,
thoughts: mpsc::SyncSender<String>,
}
// ANCHOR: Philosopher-think
impl Philosopher {
fn think(&self) {
self.thoughts
.send(format!("Eureka! {} has a new idea!", &self.name))
.unwrap();
}
// ANCHOR_END: Philosopher-think
// ANCHOR: Philosopher-eat
fn eat(&self) {
// ANCHOR_END: Philosopher-eat
println!("{} is trying to eat", &self.name);
let left = self.left_fork.lock().unwrap();
let right = self.right_fork.lock().unwrap();
// ANCHOR: Philosopher-eat-end
println!("{} is eating...", &self.name);
thread::sleep(Duration::from_millis(10));
}
}
static PHILOSOPHERS: &[&str] =
&["Socrates", "Plato", "Aristotle", "Thales", "Pythagoras"];
fn main() {
// ANCHOR_END: Philosopher-eat-end
let (tx, rx) = mpsc::sync_channel(10);
let forks = (0..PHILOSOPHERS.len())
.map(|_| Arc::new(Mutex::new(Fork)))
.collect::<Vec<_>>();
for i in 0..forks.len() {
let tx = tx.clone();
let mut left_fork = forks[i].clone();
let mut right_fork = forks[(i + 1) % forks.len()].clone();
// To avoid a deadlock, we have to break the symmetry
// somewhere. This will swap the forks without deinitializing
// either of them.
if i == forks.len() - 1 {
std::mem::swap(&mut left_fork, &mut right_fork);
}
let philosopher = Philosopher {
name: PHILOSOPHERS[i].to_string(),
thoughts: tx,
left_fork,
right_fork,
};
thread::spawn(move || {
for _ in 0..100 {
philosopher.eat();
philosopher.think();
}
});
}
drop(tx);
for thought in rx {
println!("{}", thought);
}
}

View File

@ -0,0 +1,80 @@
# Multi-threaded Link Checker
Let us use our new knowledge to create a multi-threaded link checker. It should
start at a webpage and check that links on the page are valid. It should
recursively check other pages on the same domain and keep doing this until all
pages have been validated.
For this, you will need an HTTP client such as [`reqwest`][1]. Create a new
Cargo project and `reqwest` it as a dependency with:
```shell
$ cargo new link-checker
$ cd link-checker
$ cargo add --features blocking reqwest
```
> If `cargo add` fails with `error: no such subcommand`, then please edit the
> `Cargo.toml` file by hand. Add the dependencies listed below.
You will also need a way to find links. We can use [`scraper`][2] for that:
```shell
$ cargo add scraper
```
Finally, we'll need some way of handling errors. We [`thiserror`][3] for that:
```shell
$ cargo add thiserror
```
The `cargo add` calls will update the `Cargo.toml` file to look like this:
```toml
[dependencies]
reqwest = { version = "0.11.12", features = ["blocking"] }
scraper = "0.13.0"
thiserror = "1.0.37"
```
You can now download the start page. Try with a small site such as
`https://www.google.org/`.
Your `src/main.rs` file should look something like this:
```rust,compile_fail
{{#include link-checker.rs:setup}}
{{#include link-checker.rs:extract_links}}
fn main() {
let start_url = Url::parse("https://www.google.org").unwrap();
let response = get(start_url).unwrap();
match extract_links(response) {
Ok(links) => println!("Links: {links:#?}"),
Err(err) => println!("Could not extract links: {err:#}"),
}
}
```
Run the code in `src/main.rs` with
```shell
$ cargo run
```
> If `cargo run` fails with an error mentioning OpenSSL, ensure that you have
> the OpenSSL headers installed by running `sudo apt install libssl-dev`.
## Tasks
* Use threads to check the links in parallel: send the URLs to be checked to a
channel and let a few threads check the URLs in parallel.
* Extend this to recursively extract links from all pages on the
`www.google.org` domain. Put an upper limit of 100 pages or so so that you
don't end up being blocked by the site.
[1]: https://docs.rs/reqwest/
[2]: https://docs.rs/scraper/
[3]: https://docs.rs/thiserror/

View File

@ -0,0 +1,89 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: setup
use reqwest::blocking::{get, Response};
use reqwest::Url;
use scraper::{Html, Selector};
use thiserror::Error;
#[derive(Error, Debug)]
enum Error {
#[error("request error: {0}")]
ReqwestError(#[from] reqwest::Error),
}
// ANCHOR_END: setup
// ANCHOR: extract_links
fn extract_links(response: Response) -> Result<Vec<Url>, Error> {
let base_url = response.url().to_owned();
let document = response.text()?;
let html = Html::parse_document(&document);
let selector = Selector::parse("a").unwrap();
let mut valid_urls = Vec::new();
for element in html.select(&selector) {
if let Some(href) = element.value().attr("href") {
match base_url.join(href) {
Ok(url) => valid_urls.push(url),
Err(err) => {
println!("On {base_url}: could not parse {href:?}: {err} (ignored)",);
}
}
}
}
Ok(valid_urls)
}
// ANCHOR_END: extract_links
fn check_links(url: Url) -> Result<Vec<Url>, Error> {
println!("Checking {url}");
let response = get(url.to_owned())?;
if !response.status().is_success() {
return Ok(vec![url.to_owned()]);
}
let links = extract_links(response)?;
for link in &links {
println!("{link}, {:?}", link.domain());
}
let mut failed_links = Vec::new();
for link in links {
if link.domain() != url.domain() {
println!("Checking external link: {link}");
let response = get(link.clone())?;
if !response.status().is_success() {
println!("Error on {url}: {link} failed: {}", response.status());
failed_links.push(link);
}
} else {
println!("Checking link in same domain: {link}");
failed_links.extend(check_links(link)?)
}
}
Ok(failed_links)
}
fn main() {
let start_url = Url::parse("https://www.google.org").unwrap();
match check_links(start_url) {
Ok(links) => println!("Links: {links:#?}"),
Err(err) => println!("Could not extract links: {err:#}"),
}
}

View File

@ -0,0 +1,8 @@
# Exercises
Let us practice our new concurrency skills with
* Dining philosophers: a classic problems in concurrency.
* Multi-threaded link checker: a larger project where you'll use Cargo to
download dependencies and then check links in parallel.

View File

@ -0,0 +1,10 @@
# Day 4 Morning Exercise
## Dining Philosophers
([back to exercise](dining-philosophers.md))
```rust
{{#include dining-philosophers.rs}}
```

View File

@ -0,0 +1,12 @@
# Solutions
You will find solutions to the exercises on the following pages.
Feel free to ask questions about the solutions [on
GitHub](https://github.com/google/comprehensive-rust/discussions). Let us know
if you have a different or better solution than what is presented here.
> **Note:** Please ignore the `// ANCHOR: label` and `// ANCHOR_END: label`
> comments you see in the solutions. They are there to make it possible to
> re-use parts of the solutions as the exercises.