ジェネリックなデータ型
ジェネリクスを使うと、具体的なフィールド型を抽象化できます。前の セグメントの演習に戻ると、次のようになります:
// 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ブロックにだけ付けます。