ジェネリックなデータ型

ジェネリクスを使うと、具体的なフィールド型を抽象化できます。前の セグメントの演習に戻ると、次のようになります:

// Copyright 2023 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!("verbosity={verbosity}: {message}");
    }
}

/// 指定された詳細度レベル以下のメッセージだけをログに記録します。
struct VerbosityFilter<L> {
    max_verbosity: u8,
    inner: L,
}

impl<L: Logger> Logger for VerbosityFilter<L> {
    fn log(&self, verbosity: u8, message: &str) {
        if verbosity <= self.max_verbosity {
            self.inner.log(verbosity, message);
        }
    }
}

fn main() {
    let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
    logger.log(5, "FYI");
    logger.log(2, "Uhoh");
}
  • Q: なぜ impl<L: Logger> .. VerbosityFilter<L> では L を 2 回指定して いるのですか? 冗長ではないのですか?
    • これは、ジェネリックな型に対するジェネリックな実装ブロックだから です。これらは独立してジェネリックです。
    • これは、これらのメソッドが任意の L に対して定義されることを 意味します。
    • impl VerbosityFilter<StderrLogger> { .. } と書くこともできます。
      • VerbosityFilter 自体は引き続きジェネリックなので VerbosityFilter<f64> も使えますが、このブロック内のメソッドは VerbosityFilter<StderrLogger> に対してのみ利用できます。
  • VerbosityFilter 型そのものにはトレイト境界を付けていない点に注意して ください。そこに境界を付けることもできますが、一般に Rust ではトレイト 境界は impl ブロックにだけ付けます。