Skip to content

Commit

Permalink
Miscellaneous minor improvements (#2370)
Browse files Browse the repository at this point in the history
  • Loading branch information
randomPoison authored Sep 20, 2024
1 parent aeb643f commit 2f9babd
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 37 deletions.
12 changes: 12 additions & 0 deletions src/control-flow-basics/break-continue/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@ fn main() {
print!("elements searched: {elements_searched}");
}
```

<details>

- Labeled break also works on arbitrary blocks, e.g.
```rust
'label: {
break 'label;
println!("This line gets skipped");
}
```

</details>
4 changes: 2 additions & 2 deletions src/control-flow-basics/exercise.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ initial `n`.
todo!("Implement this")
}
{{#include exercise.rs:tests}}
{{#include exercise.rs:main}}
todo!("Implement this")
}
```
2 changes: 1 addition & 1 deletion src/control-flow-basics/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ fn test_collatz_length() {

// ANCHOR: main
fn main() {
// ANCHOR_END: main
println!("Length: {}", collatz_length(11));
}
// ANCHOR_END: main
13 changes: 12 additions & 1 deletion src/generics/generic-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@ fn pick<T>(n: i32, even: T, odd: T) -> T {
fn main() {
println!("picked a number: {:?}", pick(97, 222, 333));
println!("picked a tuple: {:?}", pick(28, ("dog", 1), ("cat", 2)));
println!("picked a string: {:?}", pick(28, "dog", "cat"));
}
```

<details>

- Rust infers a type for T based on the types of the arguments and return value.

- In this example we only use the primitive types `i32` and `&str` for `T`, but
we can use any type here, including user-defined types:

```rust,ignore
struct Foo {
val: u8,
}
pick(123, Foo { val: 7 }, Foo { val: 456 });
```

- This is similar to C++ templates, but Rust partially compiles the generic
function immediately, so that function must be valid for all types matching
the constraints. For example, try modifying `pick` to return `even + odd` if
Expand Down
18 changes: 6 additions & 12 deletions src/methods-and-traits/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,18 @@

// ANCHOR: solution
// ANCHOR: setup
use std::fmt::Display;

pub trait Logger {
/// Log a message at the given verbosity level.
fn log(&self, verbosity: u8, message: impl Display);
fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
fn log(&self, verbosity: u8, message: impl Display) {
fn log(&self, verbosity: u8, message: &str) {
eprintln!("verbosity={verbosity}: {message}");
}
}

fn do_things(logger: &impl Logger) {
logger.log(5, "FYI");
logger.log(2, "Uhoh");
}
// ANCHOR_END: setup

/// Only log messages up to the given verbosity level.
Expand All @@ -42,7 +35,7 @@ struct VerbosityFilter {
}

impl Logger for VerbosityFilter {
fn log(&self, verbosity: u8, message: impl Display) {
fn log(&self, verbosity: u8, message: &str) {
if verbosity <= self.max_verbosity {
self.inner.log(verbosity, message);
}
Expand All @@ -51,7 +44,8 @@ impl Logger for VerbosityFilter {

// ANCHOR: main
fn main() {
let l = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
do_things(&l);
let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
logger.log(5, "FYI");
logger.log(2, "Uhoh");
}
// ANCHOR_END: main
7 changes: 0 additions & 7 deletions src/pattern-matching/destructuring-enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,5 @@ arm, `half` is bound to the value inside the `Ok` variant. In the second arm,
matched.
- Demonstrate what happens when the search is inexhaustive. Note the advantage
the Rust compiler provides by confirming when all cases are handled.
- Save the result of `divide_in_two` in the `result` variable and `match` it in
a loop. That won't compile because `msg` is consumed when matched. To fix it,
match `&result` instead of `result`. That will make `msg` a reference so it
won't be consumed. This
["match ergonomics"](https://rust-lang.github.io/rfcs/2005-match-ergonomics.html)
appeared in Rust 2018. If you want to support older Rust, replace `msg` with
`ref msg` in the pattern.

</details>
4 changes: 2 additions & 2 deletions src/pattern-matching/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ fn eval(e: Expression) -> Result<i64, String> {
Expression::Op { op, left, right } => {
let left = match eval(*left) {
Ok(v) => v,
e @ Err(_) => return e,
Err(e) => return Err(e),
};
let right = match eval(*right) {
Ok(v) => v,
e @ Err(_) => return e,
Err(e) => return Err(e),
};
Ok(match op {
Operation::Add => left + right,
Expand Down
20 changes: 20 additions & 0 deletions src/pattern-matching/match.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,24 @@ Key Points:
- The condition defined in the guard applies to every expression in a pattern
with an `|`.

# More To Explore

- Another piece of pattern syntax you can show students is the `@` syntax which
binds a part of a pattern to a variable. For example:

```rust
let opt = Some(123);
match opt {
outer @ Some(inner) => {
println!("outer: {outer:?}, inner: {inner}");
}
None => {}
}
```

In this example `inner` has the value 123 which it pulled from the `Option`
via destructuring, `outer` captures the entire `Some(inner)` expression, so it
contains the full `Option::Some(123)`. This is rarely used but can be useful
in more complex patterns.

</details>
2 changes: 2 additions & 0 deletions src/references/shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ fn x_axis(x: &i32) -> &(i32, i32) {

<details>

- References can never be null in Rust, so null checking is not necessary.

- A reference is said to "borrow" the value it refers to, and this is a good
model for students not familiar with pointers: code can use the reference to
access the value, but is still "owned" by the original variable. The course
Expand Down
7 changes: 0 additions & 7 deletions src/references/slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ fn main() {
```

- Slices borrow data from the sliced type.
- Question: What happens if you modify `a[3]` right before printing `s`?

<details>

Expand All @@ -43,10 +42,4 @@ fn main() {
- Slices always borrow from another object. In this example, `a` has to remain
'alive' (in scope) for at least as long as our slice.

- The question about modifying `a[3]` can spark an interesting discussion, but
the answer is that for memory safety reasons you cannot do it through `a` at
this point in the execution, but you can read the data from both `a` and `s`
safely. It works before you created the slice, and again after the `println`,
when the slice is no longer used.

</details>
5 changes: 0 additions & 5 deletions src/std-types/hashmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@ fn main() {

- Alternatively HashMap can be built from any `Iterator` which yields key-value
tuples.
- We are showing `HashMap<String, i32>`, and avoid using `&str` as key to make
examples easier. Using references in collections can, of course, be done, but
it can lead into complications with the borrow checker.
- Try removing `to_string()` from the example above and see if it still
compiles. Where do you think we might run into issues?

- This type has several "method-specific" return types, such as
`std::collections::hash_map::Keys`. These types often appear in searches of
Expand Down

0 comments on commit 2f9babd

Please sign in to comment.