Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Решение

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

pub trait Logger {
    /// Записывает сообщение с заданным уровнем подробности.
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

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

struct StdoutLogger;

impl Logger for StdoutLogger {
    fn log(&self, verbosity: u8, message: &str) {
        println!("[stdout] verbosity={verbosity}: {message}");
    }
}

/// Записывает только сообщения, которые соответствуют предикату фильтрации.
struct Filter<L, P> {
    inner: L,
    predicate: P,
}

impl<L, P> Filter<L, P>
where
    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 prefix = "yikes";
    let max_verbosity = 3;

    // Логгер, который пишет в stderr и фильтрует по содержимому сообщений.
    let logger = Filter::new(StderrLogger, |_verbosity, msg| msg.contains(prefix));
    logger.log(5, "FYI");
    logger.log(1, "yikes, something went wrong");
    logger.log(2, "uhoh");

    // Логгер, который пишет в stdout и фильтрует по уровню подробности.
    let logger =
        Filter::new(StdoutLogger, |verbosity, _msg| verbosity <= max_verbosity);
    logger.log(5, "FYI");
    logger.log(1, "yikes, something went wrong");
    logger.log(2, "uhoh");
}
  • Хранение замыканий: чтобы сохранить замыкание в структуре, мы используем обобщённый параметр типа (здесь P). Это потому, что каждое замыкание в Rust имеет уникальный анонимный тип, который генерирует компилятор.
  • Ограничение Fn: ограничение P: Fn(u8, &str) -> bool сообщает компилятору, что P можно вызвать как функцию с указанными аргументами и возвращаемым типом. Мы используем Fn (а не FnMut или FnOnce), потому что log принимает &self, поэтому предикат можно использовать только неизменяемо.
  • Вызов полей: мы вызываем замыкание через (self.predicate)(...). Скобки вокруг self.predicate необходимы, чтобы отличить вызов метода с именем predicate от вызова самого поля.
  • Обсудите, почему нужен Fn. Если бы мы использовали FnMut, log пришлось бы принимать &mut self, что противоречит сигнатуре трейта Logger. Если бы мы использовали FnOnce, можно было бы записать только одно сообщение!
  • Блок impl для new тоже содержит ограничения. Хотя для самого определения структуры они строго не обязательны (ограничения можно ставить только на блоки impl, которые их используют), размещение их в new помогает выводу типов.