You've already forked comprehensive-rust
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:
committed by
GitHub
parent
ea204774b6
commit
6d19292f16
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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);
|
||||
}
|
@ -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
|
@ -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() {}
|
||||
```
|
@ -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
|
@ -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>
|
@ -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?
|
@ -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));
|
||||
}
|
@ -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}}
|
||||
```
|
@ -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
|
@ -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>
|
@ -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}}
|
||||
```
|
@ -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")
|
||||
);
|
||||
}
|
@ -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}}
|
||||
```
|
@ -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.
|
@ -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>
|
@ -1,9 +0,0 @@
|
||||
# Day 2 Afternoon Exercises
|
||||
|
||||
## Strings and Iterators
|
||||
|
||||
([back to exercise](strings-iterators.md))
|
||||
|
||||
```rust
|
||||
{{#include strings-iterators.rs:solution}}
|
||||
```
|
@ -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}}
|
||||
```
|
@ -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}}
|
||||
```
|
@ -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() {}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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() {}
|
@ -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}}
|
||||
```
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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! | |
|
||||
| +-----------+ |
|
||||
+--------------------------------+
|
||||
```
|
@ -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
|
@ -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}}
|
||||
```
|
@ -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}}
|
||||
```
|
@ -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.
|
Reference in New Issue
Block a user