You've already forked comprehensive-rust
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:
7
src/exercises/day-1/afternoon.md
Normal file
7
src/exercises/day-1/afternoon.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Day 1: Afternoon Exercises
|
||||
|
||||
We will look at two things:
|
||||
|
||||
* A small book library,
|
||||
|
||||
* Iterators and ownership (hard).
|
42
src/exercises/day-1/book-library.md
Normal file
42
src/exercises/day-1/book-library.md
Normal 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}}
|
||||
```
|
168
src/exercises/day-1/book-library.rs
Normal file
168
src/exercises/day-1/book-library.rs
Normal 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")
|
||||
);
|
||||
}
|
75
src/exercises/day-1/for-loops.md
Normal file
75
src/exercises/day-1/for-loops.md
Normal 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.
|
69
src/exercises/day-1/for-loops.rs
Normal file
69
src/exercises/day-1/for-loops.rs
Normal 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);
|
||||
}
|
41
src/exercises/day-1/implicit-conversions.md
Normal file
41
src/exercises/day-1/implicit-conversions.md
Normal 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
|
110
src/exercises/day-1/iterators-and-ownership.md
Normal file
110
src/exercises/day-1/iterators-and-ownership.md
Normal 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.
|
7
src/exercises/day-1/morning.md
Normal file
7
src/exercises/day-1/morning.md
Normal 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.
|
9
src/exercises/day-1/solutions-afternoon.md
Normal file
9
src/exercises/day-1/solutions-afternoon.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Day 1 Afternoon Exercises
|
||||
|
||||
## Designing a Library
|
||||
|
||||
([back to exercise](book-library.md))
|
||||
|
||||
```rust
|
||||
{{#include book-library.rs}}
|
||||
```
|
9
src/exercises/day-1/solutions-morning.md
Normal file
9
src/exercises/day-1/solutions-morning.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Day 1 Morning Exercises
|
||||
|
||||
## Arrays and `for` Loops
|
||||
|
||||
([back to exercise](for-loops.md))
|
||||
|
||||
```rust
|
||||
{{#include for-loops.rs}}
|
||||
```
|
3
src/exercises/day-2/afternoon.md
Normal file
3
src/exercises/day-2/afternoon.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Day 2: Afternoon Exercises
|
||||
|
||||
The exercises for this afternoon will focus on strings and iterators.
|
32
src/exercises/day-2/health-statistics.md
Normal file
32
src/exercises/day-2/health-statistics.md
Normal 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);
|
||||
}
|
||||
```
|
35
src/exercises/day-2/luhn.md
Normal file
35
src/exercises/day-2/luhn.md
Normal 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() {}
|
||||
```
|
88
src/exercises/day-2/luhn.rs
Normal file
88
src/exercises/day-2/luhn.rs
Normal 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
|
7
src/exercises/day-2/morning.md
Normal file
7
src/exercises/day-2/morning.md
Normal 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.
|
41
src/exercises/day-2/points-polygons.md
Normal file
41
src/exercises/day-2/points-polygons.md
Normal 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() {}
|
||||
```
|
223
src/exercises/day-2/points-polygons.rs
Normal file
223
src/exercises/day-2/points-polygons.rs
Normal 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
|
17
src/exercises/day-2/solutions-afternoon.md
Normal file
17
src/exercises/day-2/solutions-afternoon.md
Normal 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}}
|
||||
```
|
12
src/exercises/day-2/solutions-morning.md
Normal file
12
src/exercises/day-2/solutions-morning.md
Normal 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() {}
|
||||
```
|
21
src/exercises/day-2/strings-iterators.md
Normal file
21
src/exercises/day-2/strings-iterators.md
Normal 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}}
|
||||
```
|
75
src/exercises/day-2/strings-iterators.rs
Normal file
75
src/exercises/day-2/strings-iterators.rs
Normal 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
|
3
src/exercises/day-3/afternoon.md
Normal file
3
src/exercises/day-3/afternoon.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Day 3: Afternoon Exercises
|
||||
|
||||
Let us build a safe wrapper for reading directory content!
|
3
src/exercises/day-3/morning.md
Normal file
3
src/exercises/day-3/morning.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Day 3: Morning Exercises
|
||||
|
||||
We will design a classical GUI library traits and trait objects.
|
47
src/exercises/day-3/safe-ffi-wrapper.md
Normal file
47
src/exercises/day-3/safe-ffi-wrapper.md
Normal 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}}
|
||||
```
|
110
src/exercises/day-3/safe-ffi-wrapper.rs
Normal file
110
src/exercises/day-3/safe-ffi-wrapper.rs
Normal 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
|
91
src/exercises/day-3/simple-gui.md
Normal file
91
src/exercises/day-3/simple-gui.md
Normal 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! | |
|
||||
| +-----------+ |
|
||||
+--------------------------------+
|
||||
```
|
160
src/exercises/day-3/simple-gui.rs
Normal file
160
src/exercises/day-3/simple-gui.rs
Normal 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
|
9
src/exercises/day-3/solutions-afternoon.md
Normal file
9
src/exercises/day-3/solutions-afternoon.md
Normal 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}}
|
||||
```
|
9
src/exercises/day-3/solutions-morning.md
Normal file
9
src/exercises/day-3/solutions-morning.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Day 3 Morning Exercise
|
||||
|
||||
## A Simple GUI Library
|
||||
|
||||
([back to exercise](simple-gui.md))
|
||||
|
||||
```rust
|
||||
{{#include simple-gui.rs}}
|
||||
```
|
8
src/exercises/day-4/afternoon.md
Normal file
8
src/exercises/day-4/afternoon.md
Normal 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.
|
37
src/exercises/day-4/dining-philosophers.md
Normal file
37
src/exercises/day-4/dining-philosophers.md
Normal 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
|
||||
}
|
||||
```
|
95
src/exercises/day-4/dining-philosophers.rs
Normal file
95
src/exercises/day-4/dining-philosophers.rs
Normal 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);
|
||||
}
|
||||
}
|
80
src/exercises/day-4/link-checker.md
Normal file
80
src/exercises/day-4/link-checker.md
Normal 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/
|
89
src/exercises/day-4/link-checker.rs
Normal file
89
src/exercises/day-4/link-checker.rs
Normal 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:#}"),
|
||||
}
|
||||
}
|
8
src/exercises/day-4/morning.md
Normal file
8
src/exercises/day-4/morning.md
Normal 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.
|
10
src/exercises/day-4/solutions-morning.md
Normal file
10
src/exercises/day-4/solutions-morning.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Day 4 Morning Exercise
|
||||
|
||||
## Dining Philosophers
|
||||
|
||||
([back to exercise](dining-philosophers.md))
|
||||
|
||||
```rust
|
||||
{{#include dining-philosophers.rs}}
|
||||
```
|
||||
|
12
src/exercises/solutions.md
Normal file
12
src/exercises/solutions.md
Normal 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.
|
Reference in New Issue
Block a user