解答

// 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");
}
  • クロージャの格納: クロージャを構造体に格納するには、ジェネリック型 パラメータ(ここでは P)を使います。これは、Rust の各クロージャが コンパイラによって生成される一意の無名型を持つためです。
  • Fn トレイト境界: 境界 P: Fn(u8, &str) -> bool は、P が 指定された引数と戻り値の型で関数として呼び出せることをコンパイラに 伝えます。log&self を受け取るため、predicate には不変にしか アクセスできないので、FnFnMutFnOnce ではなく)を使います。
  • フィールドの呼び出し: (self.predicate)(...) を使ってクロージャを 呼び出します。self.predicate を囲む括弧は、predicate という名前の メソッド呼び出しとフィールド自体の呼び出しを区別するために必要です。
  • Fn が必要な理由を説明しましょう。FnMut を使うと、log&mut self を受け取る必要があり、これは Logger トレイトの シグネチャと衝突します。FnOnce を使うと、1 件のメッセージしか ログに記録できません!
  • newimpl ブロックにもトレイト境界が含まれています。技術的には、 これは構造体定義そのものには厳密には必須ではありません(トレイト境界は、 それらを使用する impl ブロックにのみ置くこともできます)が、new に 付けておくと型推論に役立ちます。