diff --git a/Cargo.lock b/Cargo.lock
index d80d64cb..4acd9b59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1355,6 +1355,10 @@ dependencies = [
"syn 2.0.48",
]
+[[package]]
+name = "modules"
+version = "0.1.0"
+
[[package]]
name = "native-tls"
version = "0.2.11"
diff --git a/Cargo.toml b/Cargo.toml
index 600c3481..ec55bef7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ members = [
"src/std-types",
"src/std-traits",
"src/iterators",
+ "src/modules",
"src/testing",
"src/memory-management",
"src/smart-pointers",
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 758b685d..11b19264 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -78,7 +78,7 @@
- [Traits](methods-and-traits/traits.md)
- [Deriving](methods-and-traits/deriving.md)
- [Trait Objects](methods-and-traits/trait-objects.md)
- - [Exercise: GUI Library](methods-and-traits/exercise.md)
+ - [Exercise: Generic Logger](methods-and-traits/exercise.md)
- [Solution](methods-and-traits/solution.md)
- [Generics](generics.md)
- [Generic Functions](generics/generic-functions.md)
@@ -167,7 +167,7 @@
- [Filesystem Hierarchy](modules/filesystem.md)
- [Visibility](modules/visibility.md)
- [`use`, `super`, `self`](modules/paths.md)
- - [Exercise: Modules for the GUI Library](modules/exercise.md)
+ - [Exercise: Modules for a GUI Library](modules/exercise.md)
- [Solution](modules/solution.md)
- [Testing](testing.md)
- [Test Modules](testing/unit-tests.md)
diff --git a/src/methods-and-traits/exercise.md b/src/methods-and-traits/exercise.md
index d20f189f..c11faad6 100644
--- a/src/methods-and-traits/exercise.md
+++ b/src/methods-and-traits/exercise.md
@@ -1,75 +1,26 @@
---
-minutes: 30
+minutes: 20
---
-# Exercise: GUI Library
+# Exercise: Generic Logger
-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.
+Let's design a simple logging utility, using a trait `Logger` with a `log`
+method. Code which might log its progress can then take an `&impl Logger`. In
+testing, this might put messages in the test logfile, while in a production
+build it would send messages to a log server.
-We will have a number of widgets in our library:
+However, the `StderrLogger` given below logs all messages, regardless of
+verbosity. Your task is to write a `VerbosityFilter` type that will ignore
+messages above a maximum verbosity.
-- `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 , fill in the missing
-`draw_into` methods so that you implement the `Widget` trait:
+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?
```rust,compile_fail
-// TODO: remove this when you're done with your implementation.
-#![allow(unused_imports, unused_variables, dead_code)]
-
{{#include exercise.rs:setup}}
-// TODO: Implement `Widget` for `Label`.
-
-// TODO: Implement `Widget` for `Button`.
-
-// TODO: Implement `Widget` for `Window`.
+// TODO: Define and implement `VerbosityFilter`.
{{#include exercise.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");
-}
-```
-
-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! | |
-| +-----------+ |
-+--------------------------------+
-```
diff --git a/src/methods-and-traits/exercise.rs b/src/methods-and-traits/exercise.rs
index 78dbe277..f432c98b 100644
--- a/src/methods-and-traits/exercise.rs
+++ b/src/methods-and-traits/exercise.rs
@@ -1,4 +1,4 @@
-// Copyright 2022 Google LLC
+// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,124 +14,44 @@
// ANCHOR: solution
// ANCHOR: setup
-pub trait Widget {
- /// Natural width of `self`.
- fn width(&self) -> usize;
+use std::fmt::Display;
- /// Draw the widget into a buffer.
- fn draw_into(&self, buffer: &mut dyn std::fmt::Write);
+pub trait Logger {
+ /// Log a message at the given verbosity level.
+ fn log(&self, verbosity: u8, message: impl Display);
+}
- /// Draw the widget on standard output.
- fn draw(&self) {
- let mut buffer = String::new();
- self.draw_into(&mut buffer);
- println!("{buffer}");
+struct StderrLogger;
+
+impl Logger for StderrLogger {
+ fn log(&self, verbosity: u8, message: impl Display) {
+ eprintln!("verbosity={verbosity}: {message}");
}
}
-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>,
-}
-
-impl Window {
- fn new(title: &str) -> Window {
- Window { title: title.to_owned(), widgets: Vec::new() }
- }
-
- fn add_widget(&mut self, widget: Box) {
- 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),
- )
- }
+fn do_things(logger: &impl Logger) {
+ logger.log(5, "FYI");
+ logger.log(2, "Uhoh");
}
// ANCHOR_END: setup
-impl Widget for Window {
- fn width(&self) -> usize {
- // Add 4 paddings for borders
- self.inner_width() + 4
- }
-
- fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
- 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, "+-{:- {
+ max_verbosity: u8,
+ inner: L,
}
-impl Widget for Button {
- fn width(&self) -> usize {
- self.label.width() + 8 // add a bit of padding
- }
-
- fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
- let width = self.width();
- let mut label = String::new();
- self.label.draw_into(&mut label);
-
- writeln!(buffer, "+{:- Logger for VerbosityFilter {
+ fn log(&self, verbosity: u8, message: impl Display) {
+ if verbosity <= self.max_verbosity {
+ self.inner.log(verbosity, message);
}
- writeln!(buffer, "+{:- usize {
- self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
- }
-
- fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
- 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();
+ let l = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
+ do_things(&l);
}
// ANCHOR_END: main
diff --git a/src/modules/Cargo.toml b/src/modules/Cargo.toml
new file mode 100644
index 00000000..a86b0386
--- /dev/null
+++ b/src/modules/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "modules"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "modules"
+path = "exercise.rs"
diff --git a/src/modules/exercise.md b/src/modules/exercise.md
index 82d0bc3a..5a5a7b5f 100644
--- a/src/modules/exercise.md
+++ b/src/modules/exercise.md
@@ -1,16 +1,15 @@
---
-minutes: 20
+minutes: 15
---
-# Exercise: Modules for the GUI Library
+# Exercise: Modules for a GUI Library
-In this exercise, you will reorganize the GUI Library exercise from the "Methods
-and Traits" segment of the course into a collection of modules. It is typical to
-put each type or set of closely-related types into its own module, so each
-widget type should get its own module.
+In this exercise, you will reorganize a small GUI Library implementation. This
+library defines a `Widget` trait and a few implementations of that trait, as
+well as a `main` function.
-If you no longer have your version, that's fine - refer back to the
-[provided solution](../methods-and-traits/solution.html).
+It is typical to put each type or set of closely-related types into its own
+module, so each widget type should get its own module.
## Cargo Setup
@@ -23,8 +22,16 @@ cd gui-modules
cargo run
```
-Edit `src/main.rs` to add `mod` statements, and add additional files in the
-`src` directory.
+Edit the resulting `src/main.rs` to add `mod` statements, and add additional
+files in the `src` directory.
+
+## Source
+
+Here's the single-module implementation of the GUI library:
+
+```rust
+{{#include exercise.rs:single-module}}
+```
diff --git a/src/modules/exercise.rs b/src/modules/exercise.rs
new file mode 100644
index 00000000..09fbb19f
--- /dev/null
+++ b/src/modules/exercise.rs
@@ -0,0 +1,132 @@
+// 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: single-module
+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>,
+}
+
+impl Window {
+ fn new(title: &str) -> Window {
+ Window { title: title.to_owned(), widgets: Vec::new() }
+ }
+
+ fn add_widget(&mut self, widget: Box) {
+ 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),
+ )
+ }
+}
+
+impl Widget for Window {
+ fn width(&self) -> usize {
+ // Add 4 paddings for borders
+ self.inner_width() + 4
+ }
+
+ fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
+ let mut inner = String::new();
+ for widget in &self.widgets {
+ widget.draw_into(&mut inner);
+ }
+
+ let inner_width = self.inner_width();
+
+ // TODO: Change draw_into to return Result<(), std::fmt::Error>. Then use the
+ // ?-operator here instead of .unwrap().
+ writeln!(buffer, "+-{:- usize {
+ self.label.width() + 8 // add a bit of padding
+ }
+
+ fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
+ let width = self.width();
+ let mut label = String::new();
+ self.label.draw_into(&mut label);
+
+ writeln!(buffer, "+{:- usize {
+ self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
+ }
+
+ fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
+ writeln!(buffer, "{}", &self.label).unwrap();
+ }
+}
+
+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();
+}