// 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 помогает выводу типов.