Solution

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

struct StderrLogger;

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

/// Only log messages matching a filtering predicate.
struct Filter<L, P> {
    inner: L,
    predicate: P,
}

impl<L, P> Filter<L, P>
where
    L: Logger,
    P: Fn(u8, &str) -> bool,
{
    fn new(inner: L, predicate: P) -> Self {
        Self { inner, predicate }
    }
}
impl<L, P> Logger for Filter<L, P>
where
    L: Logger,
    P: Fn(u8, &str) -> bool,
{
    fn log(&self, verbosity: u8, message: &str) {
        if (self.predicate)(verbosity, message) {
            self.inner.log(verbosity, message);
        }
    }
}

fn main() {
    let logger = Filter::new(StderrLogger, |_verbosity, msg| msg.contains("yikes"));
    logger.log(5, "FYI");
    logger.log(1, "yikes, something went wrong");
    logger.log(2, "uhoh");
}
  • Storing Closures: To store a closure in a struct, we use a generic type parameter (here P). This is because every closure in Rust has a unique, anonymous type generated by the compiler.
  • Fn Trait Bound: The bound P: Fn(u8, &str) -> bool tells the compiler that P can be called as a function with the specified arguments and return type. We use Fn (instead of FnMut or FnOnce) because log takes &self, so we can only access the predicate immutably.
  • Calling fields: We invoke the closure using (self.predicate)(...). The parentheses around self.predicate are necessary to disambiguate between calling a method named predicate and calling the field itself.
  • Discuss why Fn is required. If we used FnMut, log would need to take &mut self, which conflicts with the Logger trait signature. If we used FnOnce, we could only log a single message!
  • The impl block for new also includes the bounds. While technically not strictly required for the struct definition itself (bounds can be placed only on impl blocks that use them), putting them on new helps type inference.