Skip to content

Commit

Permalink
Be clear that the methods-and-traits exercise does not require generics
Browse files Browse the repository at this point in the history
When teaching the course, I got a little tripped up thinking students would
need to make the `VerbosityFilter` generic over `Logger`. Let's be
clearer that this is not required, and will be described later.

This also updates the generic-types slide to repeat the exercise,
completing that thought.
  • Loading branch information
djmitche committed Jan 19, 2025
1 parent 68e1ebd commit b05088b
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 29 deletions.
56 changes: 32 additions & 24 deletions src/generics/generic-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,54 @@ minutes: 10

# Generic Data Types

You can use generics to abstract over the concrete field type:
You can use generics to abstract over the concrete field type. Returning to the
exercise for the previous segment:

```rust,editable
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
pub trait Logger {
/// Log a message at the given verbosity level.
fn log(&self, verbosity: u8, message: &str);
}
impl<T> Point<T> {
fn coords(&self) -> (&T, &T) {
(&self.x, &self.y)
struct StderrLogger;
impl Logger for StderrLogger {
fn log(&self, verbosity: u8, message: &str) {
eprintln!("verbosity={verbosity}: {message}");
}
}
/// Only log messages up to the given verbosity level.
struct VerbosityFilter<L: Logger> {
max_verbosity: u8,
inner: L,
}
fn set_x(&mut self, x: T) {
self.x = x;
impl<L: Logger> Logger for VerbosityFilter<L> {
fn log(&self, verbosity: u8, message: &str) {
if verbosity <= self.max_verbosity {
self.inner.log(verbosity, message);
}
}
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
println!("{integer:?} and {float:?}");
println!("coords: {:?}", integer.coords());
let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
logger.log(5, "FYI");
logger.log(2, "Uhoh");
}
```

<details>

- _Q:_ Why `T` is specified twice in `impl<T> Point<T> {}`? Isn't that
redundant?
- _Q:_ Why `L` is specified twice in `impl<L: Logger> .. VerbosityFilter<L>`?
Isn't that redundant?
- This is because it is a generic implementation section for generic type.
They are independently generic.
- It means these methods are defined for any `T`.
- It is possible to write `impl Point<u32> { .. }`.
- `Point` is still generic and you can use `Point<f64>`, but methods in this
block will only be available for `Point<u32>`.

- Try declaring a new variable `let p = Point { x: 5, y: 10.0 };`. Update the
code to allow points that have elements of different types, by using two type
variables, e.g., `T` and `U`.
- It means these methods are defined for any `L`.
- It is possible to write `impl VerbosityFilter<StderrLogger> { .. }`.
- `VerbosityFilter` is still generic and you can use `VerbosityFilter<f64>`,
but methods in this block will only be available for
`Point<StderrLogger>`.

</details>
9 changes: 5 additions & 4 deletions src/methods-and-traits/exercise.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
minutes: 20
minutes: 15
---

# Exercise: Logger Trait
Expand All @@ -14,13 +14,14 @@ verbosity. Your task is to write a `VerbosityFilter` type that will ignore
messages above a maximum verbosity.

This is a common pattern: a struct wrapping a trait implementation and
implementing that same trait, adding behavior in the process. What other kinds
of wrappers might be useful in a logging utility?
implementing that same trait, adding behavior in the process. In the "Generics"
segment this afternoon, we will see how to make the wrapper generic over the
wrapped type.

```rust,compile_fail
{{#include exercise.rs:setup}}
// TODO: Define and implement `VerbosityFilter`.
// TODO: Implement the `Logger` trait for `VerbosityFilter`.
{{#include exercise.rs:main}}
```
2 changes: 1 addition & 1 deletion src/methods-and-traits/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ impl Logger for StderrLogger {
eprintln!("verbosity={verbosity}: {message}");
}
}
// ANCHOR_END: setup

/// Only log messages up to the given verbosity level.
struct VerbosityFilter {
max_verbosity: u8,
inner: StderrLogger,
}
// ANCHOR_END: setup

impl Logger for VerbosityFilter {
fn log(&self, verbosity: u8, message: &str) {
Expand Down

0 comments on commit b05088b

Please sign in to comment.