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

Comprehensive Rust v2 (#1073)

I've taken some work by @fw-immunant and others on the new organization
of the course and condensed it into a form amenable to a text editor and
some computational analysis. You can see the inputs in `course.py` but
the interesting bits are the output: `outline.md` and `slides.md`.

The idea is to break the course into more, smaller segments with
exercises at the ends and breaks in between. So `outline.md` lists the
segments, their duration, and sums those durations up per-day. It shows
we're about an hour too long right now! There are more details of the
segments in `slides.md`, or you can see mostly the same stuff in
`course.py`.

This now contains all of the content from the v1 course, ensuring both
that we've covered everything and that we'll have somewhere to redirect
every page.

Fixes #1082.
Fixes #1465.

---------

Co-authored-by: Nicole LeGare <dlegare.1001@gmail.com>
Co-authored-by: Martin Geisler <mgeisler@google.com>
This commit is contained in:
Dustin J. Mitchell
2023-11-29 10:39:24 -05:00
committed by GitHub
parent ea204774b6
commit 6d19292f16
309 changed files with 6807 additions and 4281 deletions

View File

@ -4,38 +4,6 @@ version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "for-loops"
path = "day-1/for-loops.rs"
[[bin]]
name = "luhn"
path = "day-1/luhn.rs"
[[bin]]
name = "book-library"
path = "day-2/book-library.rs"
[[bin]]
name = "health-statistics"
path = "../../third_party/rust-on-exercism/health-statistics.rs"
[[bin]]
name = "strings-iterators"
path = "day-2/strings-iterators.rs"
[[bin]]
name = "simple-gui"
path = "day-3/simple-gui.rs"
[[bin]]
name = "points-polygons"
path = "day-3/points-polygons.rs"
[[bin]]
name = "safe-ffi-wrapper"
path = "day-3/safe-ffi-wrapper.rs"
[[bin]]
name = "dining-philosophers"
path = "concurrency/dining-philosophers.rs"

View File

@ -41,8 +41,8 @@ impl Philosopher {
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();
let _left = self.left_fork.lock().unwrap();
let _right = self.right_fork.lock().unwrap();
// ANCHOR: Philosopher-eat-end
println!("{} is eating...", &self.name);

View File

@ -13,7 +13,7 @@
// limitations under the License.
// ANCHOR: solution
use std::{sync::Arc, sync::Mutex, sync::mpsc, thread};
use std::{sync::mpsc, sync::Arc, sync::Mutex, thread};
// ANCHOR: setup
use reqwest::{blocking::Client, Url};
@ -138,7 +138,10 @@ fn control_crawl(
result_receiver: mpsc::Receiver<CrawlResult>,
) -> Vec<Url> {
let mut crawl_state = CrawlState::new(&start_url);
let start_command = CrawlCommand { url: start_url, extract_links: true };
let start_command = CrawlCommand {
url: start_url,
extract_links: true,
};
command_sender.send(start_command).unwrap();
let mut pending_urls = 1;

View File

@ -1,15 +0,0 @@
# Day 1: Afternoon Exercises
We will look at two things:
* The Luhn algorithm,
* An exercise on pattern matching.
<details>
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-afternoon.md
</details>

View File

@ -1,94 +0,0 @@
# 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 `{:?}`:
<!-- mdbook-xgettext: skip -->
```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):
<!-- mdbook-xgettext: skip -->
```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.
<details>
The solution and the answer to the bonus section are available in the
[Solution](solutions-morning.md#arrays-and-for-loops) section.
The use of the reference `&array` within `for n in &array` is a subtle
preview of issues of ownership that will come later in the afternoon.
Without the `&`...
* The loop would have been one that consumes the array. This is a
change [introduced in the 2021
Edition](https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterator-for-arrays.html).
* An implicit array copy would have occurred. Since `i32` is a copy type, then
`[i32; 3]` is also a copy type.
</details>

View File

@ -1,70 +0,0 @@
// 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: solution
// 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 makes 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

@ -1,46 +0,0 @@
# Implicit Conversions
Rust will not automatically apply _implicit conversions_ between types ([unlike
C++][3]). You can see this in a program like this:
<!-- mdbook-xgettext: skip -->
```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 a variable `x` of type `i8` to an `i16` by calling
`i16::from(x)`. Or, simpler, with `x.into()`, because `From<i8> for i16`
implementation automatically create an implementation of `Into<i16> for i8`.
The same applies for your own `From` implementations for your own types, so it is
sufficient to only implement `From` to get a respective `Into` implementation automatically.
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

@ -1,37 +0,0 @@
# 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`. For the number `98765`, we double `6` and `8`.
* After doubling a digit, sum the digits if the result is greater than 9. So doubling `7` becomes `14` which
becomes `1 + 4 = 5`.
* Sum all the undoubled and doubled digits.
* The credit card number is valid if the sum ends with `0`.
Copy the code below to <https://play.rust-lang.org/> and implement the function.
Try to solve the problem the "simple" way first, using `for` loops and integers.
Then, revisit the solution and try to implement it with iterators.
```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

@ -1,95 +0,0 @@
// 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: solution
// ANCHOR: luhn
pub fn luhn(cc_number: &str) -> bool {
// ANCHOR_END: luhn
let mut sum = 0;
let mut double = false;
let mut digit_seen = 0;
for c in cc_number.chars().filter(|&f| f != ' ').rev() {
if let Some(digit) = c.to_digit(10) {
if double {
let double_digit = digit * 2;
sum += if double_digit > 9 {
double_digit - 9
} else {
double_digit
};
} else {
sum += digit;
}
double = !double;
digit_seen += 1;
} else {
return false;
}
}
if digit_seen < 2 {
return false;
}
sum % 10 == 0
}
fn main() {
let cc_number = "1234 5678 1234 5670";
println!(
"Is {cc_number} a valid credit card number? {}",
if luhn(cc_number) { "yes" } else { "no" }
);
}
// ANCHOR: unit-tests
#[test]
fn test_non_digit_cc_number() {
assert!(!luhn("foo"));
assert!(!luhn("foo 0 0"));
}
#[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

@ -1,28 +0,0 @@
# Day 1: Morning Exercises
In these exercises, we will explore two parts of Rust:
* Implicit conversions between types.
* Arrays and `for` loops.
<details>
A few things to consider while solving the exercises:
* Use a local Rust installation, if possible. This way you can get
auto-completion in your editor. See the page about [Using Cargo] for details
on installing Rust.
* Alternatively, use the Rust Playground.
The code snippets are not editable on purpose: the inline code snippets lose
their state if you navigate away from the page.
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-morning.md
[Using Cargo]: ../../cargo.md
</details>

View File

@ -1,33 +0,0 @@
# Exercise: Expression Evaluation
Let's write a simple recursive evaluator for arithmetic expressions.
```rust
{{#include pattern-matching.rs:Operation}}
{{#include pattern-matching.rs:Expression}}
{{#include pattern-matching.rs:Res}}
{{#include pattern-matching.rs:eval}}
todo!()
}
{{#include pattern-matching.rs:tests}}
```
The `Box` type here is a smart pointer, and will be covered in detail later in
the course. An expression can be "boxed" with `Box::new` as seen in the tests.
To evaluate a boxed expression, use the deref operator to "unbox" it:
`eval(*boxed_expr)`.
Some expressions cannot be evaluated and will return an error. The `Res`
type represents either a successful value or an error with a message. This is
very similar to the standard-library `Result` which we will see later.
Copy and paste the code into the Rust playground, and begin implementing
`eval`. The final product should pass the tests. It may be helpful to use
`todo!()` and get the tests to pass one-by-one.
If you finish early, try writing a test that results in an integer overflow.
How could you handle this with `Res::Err` instead of a panic?

View File

@ -1,150 +0,0 @@
// Copyright 2023 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: solution
// ANCHOR: Operation
/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
Add,
Sub,
Mul,
Div,
}
// ANCHOR_END: Operation
// ANCHOR: Expression
/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
/// An operation on two subexpressions.
Op {
op: Operation,
left: Box<Expression>,
right: Box<Expression>,
},
/// A literal value
Value(i64),
}
// ANCHOR_END: Expression
// ANCHOR: Res
/// The result of evaluating an expression.
#[derive(Debug, PartialEq, Eq)]
enum Res {
/// Evaluation was successful, with the given result.
Ok(i64),
/// Evaluation failed, with the given error message.
Err(String),
}
// Allow `Ok` and `Err` as shorthands for `Res::Ok` and `Res::Err`.
use Res::{Err, Ok};
// ANCHOR_END: Res
// ANCHOR: eval
fn eval(e: Expression) -> Res {
// ANCHOR_END: eval
match e {
Expression::Op { op, left, right } => {
let left = match eval(*left) {
Ok(v) => v,
Err(msg) => return Err(msg),
};
let right = match eval(*right) {
Ok(v) => v,
Err(msg) => return Err(msg),
};
Ok(match op {
Operation::Add => left + right,
Operation::Sub => left - right,
Operation::Mul => left * right,
Operation::Div => {
if right == 0 {
return Err(String::from("division by zero"));
} else {
left / right
}
}
})
}
Expression::Value(v) => Ok(v),
}
}
// ANCHOR: tests
#[test]
fn test_value() {
assert_eq!(eval(Expression::Value(19)), Ok(19));
}
#[test]
fn test_sum() {
assert_eq!(
eval(Expression::Op {
op: Operation::Add,
left: Box::new(Expression::Value(10)),
right: Box::new(Expression::Value(20)),
}),
Ok(30)
);
}
#[test]
fn test_recursion() {
let term1 = Expression::Op {
op: Operation::Mul,
left: Box::new(Expression::Value(10)),
right: Box::new(Expression::Value(9)),
};
let term2 = Expression::Op {
op: Operation::Mul,
left: Box::new(Expression::Op {
op: Operation::Sub,
left: Box::new(Expression::Value(3)),
right: Box::new(Expression::Value(4)),
}),
right: Box::new(Expression::Value(5)),
};
assert_eq!(
eval(Expression::Op {
op: Operation::Add,
left: Box::new(term1),
right: Box::new(term2),
}),
Ok(85)
);
}
#[test]
fn test_error() {
assert_eq!(
eval(Expression::Op {
op: Operation::Div,
left: Box::new(Expression::Value(99)),
right: Box::new(Expression::Value(0)),
}),
Err(String::from("division by zero"))
);
}
// ANCHOR_END: tests
fn main() {
let expr = Expression::Op {
op: Operation::Sub,
left: Box::new(Expression::Value(20)),
right: Box::new(Expression::Value(10)),
};
println!("expr: {:?}", expr);
println!("result: {:?}", eval(expr));
}

View File

@ -1,15 +0,0 @@
# Day 1 Afternoon Exercises
## Luhn Algorithm
([back to exercise](luhn.md))
```rust
{{#include luhn.rs:solution}}
```
## Pattern matching
```rust
{{#include pattern-matching.rs:solution}}
```

View File

@ -1,47 +0,0 @@
# Day 1 Morning Exercises
## Arrays and `for` Loops
([back to exercise](for-loops.md))
```rust
{{#include for-loops.rs:solution}}
```
### Bonus question
It requires more advanced concepts. It might seem that we could use a slice-of-slices (`&[&[i32]]`) as the input type to transpose and thus make our function handle any size of matrix. However, this quickly breaks down: the return type cannot be `&[&[i32]]` since it needs to own the data you return.
You can attempt to use something like `Vec<Vec<i32>>`, but this doesn't work out-of-the-box either: it's hard to convert from `Vec<Vec<i32>>` to `&[&[i32]]` so now you cannot easily use `pretty_print` either.
Once we get to traits and generics, we'll be able to use the [`std::convert::AsRef`][1] trait to abstract over anything that can be referenced as a slice.
```rust
use std::convert::AsRef;
use std::fmt::Debug;
fn pretty_print<T, Line, Matrix>(matrix: Matrix)
where
T: Debug,
// A line references a slice of items
Line: AsRef<[T]>,
// A matrix references a slice of lines
Matrix: AsRef<[Line]>
{
for row in matrix.as_ref() {
println!("{:?}", row.as_ref());
}
}
fn main() {
// &[&[i32]]
pretty_print(&[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]);
// [[&str; 2]; 2]
pretty_print([["a", "b"], ["c", "d"]]);
// Vec<Vec<i32>>
pretty_print(vec![vec![1, 2], vec![3, 4]]);
}
```
In addition, the type itself would not enforce that the child slices are of the same length, so such variable could contain an invalid matrix.
[1]: https://doc.rust-lang.org/std/convert/trait.AsRef.html

View File

@ -1,11 +0,0 @@
# Day 2: Afternoon Exercises
The exercises for this afternoon will focus on strings and iterators.
<details>
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-afternoon.md
</details>

View File

@ -1,49 +0,0 @@
# Storing Books
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);
let midpoint = vec.len() / 2;
println!("middle value: {}", vec[midpoint]);
for item in &vec {
println!("item: {item}");
}
}
```
Use this to model a library's book collection. Copy the code below to
<https://play.rust-lang.org/> and update the types to make it compile:
```rust,should_panic
{{#include book-library.rs:setup}}
{{#include book-library.rs:Library_new}}
todo!("Initialize and return a `Library` value")
}
{{#include book-library.rs:Library_len}}
todo!("Return the length of `self.books`")
}
{{#include book-library.rs:Library_is_empty}}
todo!("Return `true` if `self.books` is empty")
}
{{#include book-library.rs:Library_add_book}}
todo!("Add a new book to `self.books`")
}
{{#include book-library.rs:Library_print_books}}
todo!("Iterate over `self.books` and print each book's title and year")
}
{{#include book-library.rs:Library_oldest_book}}
todo!("Return a reference to the oldest book (if any)")
}
}
{{#include book-library.rs:main}}
```

View File

@ -1,172 +0,0 @@
// 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: solution
// 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,
}
}
}
// Implement the methods below. Notice how the `self` parameter
// changes type to indicate the method's required level of ownership
// over the object:
//
// - `&self` for shared read-only access,
// - `&mut self` for unique and mutable access,
// - `self` for unique access by value.
impl Library {
// ANCHOR_END: setup
// ANCHOR: Library_new
fn new() -> Library {
// ANCHOR_END: Library_new
Library { books: Vec::new() }
}
// ANCHOR: Library_len
fn len(&self) -> usize {
// ANCHOR_END: Library_len
self.books.len()
}
// ANCHOR: Library_is_empty
fn is_empty(&self) -> bool {
// ANCHOR_END: Library_is_empty
self.books.is_empty()
}
// ANCHOR: Library_add_book
fn add_book(&mut self, book: Book) {
// ANCHOR_END: Library_add_book
self.books.push(book)
}
// ANCHOR: Library_print_books
fn print_books(&self) {
// ANCHOR_END: Library_print_books
for book in &self.books {
println!("{}, published in {}", book.title, book.year);
}
}
// ANCHOR: Library_oldest_book
fn oldest_book(&self) -> Option<&Book> {
// ANCHOR_END: Library_oldest_book
// Using a closure and a built-in method:
// self.books.iter().min_by_key(|book| book.year)
// Longer hand-written solution:
let mut oldest: Option<&Book> = None;
for book in self.books.iter() {
if oldest.is_none() || book.year < oldest.unwrap().year {
oldest = Some(book);
}
}
oldest
}
}
// ANCHOR: main
fn main() {
let mut library = Library::new();
println!(
"The library is empty: 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));
println!(
"The library is no longer empty: library.is_empty() -> {}",
library.is_empty()
);
library.print_books();
match library.oldest_book() {
Some(book) => println!("The oldest book is {}", book.title),
None => println!("The library is empty!"),
}
println!("The library has {} books", library.len());
library.print_books();
}
// 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

@ -1,49 +0,0 @@
# 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:setup}}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_new}}
todo!("Create a new User instance")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_name}}
todo!("Return the user's name")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_age}}
todo!("Return the user's age")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_height}}
todo!("Return the user's height")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_doctor_visits}}
todo!("Return the number of time the user has visited the doctor")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_set_age}}
todo!("Set the user's age")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_set_height}}
todo!("Set the user's height")
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}}
todo!("Update a user's statistics based on measurements from a visit to the doctor")
}
}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:main}}
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:tests}}
```

View File

@ -1,110 +0,0 @@
# 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-for-%26'a+Vec%3CT,+A%3E)
and [`impl IntoIterator for
Vec<T>`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-for-Vec%3CT,+A%3E)
to check your answers.

View File

@ -1,15 +0,0 @@
# Day 2: Morning Exercises
We will look at implementing methods in two contexts:
* Storing books and querying the collection
* Keeping track of health statistics for patients
<details>
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-morning.md
</details>

View File

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

View File

@ -1,17 +0,0 @@
# Day 2 Morning Exercises
## Designing a Library
([back to exercise](book-library.md))
```rust
{{#include book-library.rs:solution}}
```
## Health Statistics
([back to exercise](health-statistics.md))
```rust
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:solution}}
```

View File

@ -1,21 +0,0 @@
# 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

@ -1,75 +0,0 @@
// 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: solution
// ANCHOR: prefix_matches
pub fn prefix_matches(prefix: &str, request_path: &str) -> bool {
// ANCHOR_END: prefix_matches
let mut request_segments = request_path.split('/');
for prefix_segment in prefix.split('/') {
let Some(request_segment) = request_segments.next() else {
return false;
};
if request_segment != prefix_segment && prefix_segment != "*" {
return false;
}
}
true
// Alternatively, Iterator::zip() lets us iterate simultaneously over prefix
// and request segments. The zip() iterator is finished as soon as one of
// the source iterators is finished, but we need to iterate over all request
// segments. A neat trick that makes zip() work is to use map() and chain()
// to produce an iterator that returns Some(str) for each pattern segments,
// and then returns None indefinitely.
}
// 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
fn main() {}

View File

@ -1,18 +0,0 @@
# Day 3: Afternoon Exercises
Let us build a safe wrapper for reading directory content!
For this exercise, we suggest using a local dev environment instead
of the Playground. This will allow you to run your binary on your own machine.
To get started, follow the [running locally] instructions.
[running locally]: ../../cargo/running-locally.md
<details>
After looking at the exercise, you can look at the [solution] provided.
[solution]: solutions-afternoon.md
</details>

View File

@ -1,13 +0,0 @@
# Day 3: Morning Exercises
We will design a classical GUI library using traits and trait objects.
We will also look at enum dispatch with an exercise involving points and polygons.
<details>
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-morning.md
</details>

View File

@ -1,53 +0,0 @@
# 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() {}
```
<details>
Since the method signatures are missing from the problem statements, the key part
of the exercise is to specify those correctly. You don't have to modify the tests.
Other interesting parts of the exercise:
* Derive a `Copy` trait for some structs, as in tests the methods sometimes don't borrow their arguments.
* Discover that `Add` trait must be implemented for two objects to be addable via "+". Note that we do not discuss generics until Day 3.
</details>

View File

@ -1,233 +0,0 @@
// 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: solution
#[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
// Alternatively, Iterator::zip() lets us iterate over the points as pairs
// but we need to pair each point with the next one, and the last point
// with the first point. The zip() iterator is finished as soon as one of
// the source iterators is finished, a neat trick is to combine Iterator::cycle
// with Iterator::skip to create the second iterator for the zip and using map
// and sum to calculate the total length.
}
}
// 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 perimeter(&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_perimeters() {
let mut poly = Polygon::new();
poly.add_point(Point::new(12, 13));
poly.add_point(Point::new(17, 11));
poly.add_point(Point::new(16, 16));
let shapes = vec![
Shape::from(poly),
Shape::from(Circle::new(Point::new(10, 20), 5)),
];
let perimeters = shapes
.iter()
.map(Shape::perimeter)
.map(round_two_digits)
.collect::<Vec<_>>();
assert_eq!(perimeters, vec![15.48, 31.42]);
}
}
// ANCHOR_END: unit-tests
fn main() {}

View File

@ -1,70 +0,0 @@
# 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 for the `libc`
functions you would use from C to read the names of files in 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. There you find a number of
string types which you need for the exercise:
| Types | Encoding | Use |
|----------------------------|----------------|--------------------------------|
| [`str`] and [`String`] | UTF-8 | Text processing in Rust |
| [`CStr`] and [`CString`] | NUL-terminated | Communicating with C functions |
| [`OsStr`] and [`OsString`] | OS-specific | Communicating with the OS |
You will convert between all these types:
- `&str` to `CString`: you need to allocate space for a trailing `\0` character,
- `CString` to `*const i8`: you need a pointer to call C functions,
- `*const i8` to `&CStr`: you need something which can find the trailing `\0` character,
- `&CStr` to `&[u8]`: a slice of bytes is the universal interface for "some unknown data",
- `&[u8]` to `&OsStr`: `&OsStr` is a step towards `OsString`, use
[`OsStrExt`](https://doc.rust-lang.org/std/os/unix/ffi/trait.OsStrExt.html)
to create it,
- `&OsStr` to `OsString`: you need to clone the data in `&OsStr` to be able to return it and call
`readdir` again.
The [Nomicon] also has a very useful chapter about FFI.
[`std::ffi`]: https://doc.rust-lang.org/std/ffi/
[`str`]: https://doc.rust-lang.org/std/primitive.str.html
[`String`]: https://doc.rust-lang.org/std/string/struct.String.html
[`CStr`]: https://doc.rust-lang.org/std/ffi/struct.CStr.html
[`CString`]: https://doc.rust-lang.org/std/ffi/struct.CString.html
[`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
[`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.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

@ -1,179 +0,0 @@
// 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: solution
// ANCHOR: ffi
mod ffi {
use std::os::raw::{c_char, c_int};
#[cfg(not(target_os = "macos"))]
use std::os::raw::{c_long, c_ulong, c_ushort, c_uchar};
// 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 according to the Linux man page for readdir(3), where ino_t and
// off_t are resolved according to the definitions in
// /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}.
#[cfg(not(target_os = "macos"))]
#[repr(C)]
pub struct dirent {
pub d_ino: c_ulong,
pub d_off: c_long,
pub d_reclen: c_ushort,
pub d_type: c_uchar,
pub d_name: [c_char; 256],
}
// Layout according to the macOS man page for dir(5).
#[cfg(all(target_os = "macos"))]
#[repr(C)]
pub struct dirent {
pub d_fileno: u64,
pub d_seekoff: u64,
pub d_reclen: u16,
pub d_namlen: u16,
pub d_type: u8,
pub d_name: [c_char; 1024],
}
extern "C" {
pub fn opendir(s: *const c_char) -> *mut DIR;
#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))]
pub fn readdir(s: *mut DIR) -> *const dirent;
// See https://github.com/rust-lang/libc/issues/414 and the section on
// _DARWIN_FEATURE_64_BIT_INODE in the macOS man page for stat(2).
//
// "Platforms that existed before these updates were available" refers
// to macOS (as opposed to iOS / wearOS / etc.) on Intel and PowerPC.
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
#[link_name = "readdir$INODE64"]
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
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_nonexisting_directory() {
let iter = DirectoryIterator::new("no-such-directory");
assert!(iter.is_err());
}
#[test]
fn test_empty_directory() -> Result<(), Box<dyn Error>> {
let tmp = tempfile::TempDir::new()?;
let iter = DirectoryIterator::new(
tmp.path().to_str().ok_or("Non UTF-8 character in path")?,
)?;
let mut entries = iter.collect::<Vec<_>>();
entries.sort();
assert_eq!(entries, &[".", ".."]);
Ok(())
}
#[test]
fn test_nonempty_directory() -> Result<(), Box<dyn Error>> {
let tmp = tempfile::TempDir::new()?;
std::fs::write(tmp.path().join("foo.txt"), "The Foo Diaries\n")?;
std::fs::write(tmp.path().join("bar.png"), "<PNG>\n")?;
std::fs::write(tmp.path().join("crab.rs"), "//! Crab\n")?;
let iter = DirectoryIterator::new(
tmp.path().to_str().ok_or("Non UTF-8 character in path")?,
)?;
let mut entries = iter.collect::<Vec<_>>();
entries.sort();
assert_eq!(entries, &[".", "..", "bar.png", "crab.rs", "foo.txt"]);
Ok(())
}
}

View File

@ -1,92 +0,0 @@
# Drawing A Simple GUI
Let us design a classical GUI library using our new knowledge of traits and
trait objects. We'll only implement the drawing of it (as text) for simplicity.
We will have a number of widgets in our library:
* `Window`: has a `title` and contains other widgets.
* `Button`: has a `label`. In reality, it would also take a callback
function to allow the program to do something when the button is clicked
but we won't include that since we're only drawing the GUI.
* `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

@ -1,163 +0,0 @@
// 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: solution
// 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,
}
impl Button {
fn new(label: &str) -> Button {
Button {
label: Label::new(label),
}
}
}
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);
}
fn inner_width(&self) -> usize {
std::cmp::max(
self.title.chars().count(),
self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
)
}
}
// ANCHOR_END: setup
// ANCHOR: Window-width
impl Widget for Window {
fn width(&self) -> usize {
// ANCHOR_END: Window-width
// Add 4 paddings for borders
self.inner_width() + 4
}
// 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 inner_width = self.inner_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, "+-{:-<inner_width$}-+", "").unwrap();
writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
for line in inner.lines() {
writeln!(buffer, "| {:inner_width$} |", line).unwrap();
}
writeln!(buffer, "+-{:-<inner_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!"
)));
window.draw();
}
// ANCHOR_END: main

View File

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

View File

@ -1,17 +0,0 @@
# Day 3 Morning Exercise
## Drawing A Simple GUI
([back to exercise](simple-gui.md))
```rust
{{#include simple-gui.rs:solution}}
```
## Points and Polygons
([back to exercise](points-polygons.md))
```rust
{{#include points-polygons.rs:solution}}
```

View File

@ -1,7 +0,0 @@
# 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.