Display
fmt::Debug の見た目はコンパクトでもクリーンでもないことが多いため、 出力の見た目をカスタマイズすると有利な場合がよくあります。これは、 {} 出力マーカーを使う fmt::Display を手動で実装することで行います。 実装は次のようになります。
#![allow(unused)] fn main() { // `fmt` モジュールを利用できるように、(`use` で)インポートする。 use std::fmt; // `fmt::Display` を実装する構造体を定義する。これは // `i32` を含む `Structure` という名前のタプル構造体である。 struct Structure(i32); // `{}` マーカーを使うには、その型に対してトレイト `fmt::Display` を // 手動で実装する必要がある。 impl fmt::Display for Structure { // このトレイトは、この正確なシグネチャを持つ `fmt` を要求する。 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 与えられた出力ストリーム `f` に、厳密に最初の要素を書き込む。 // 操作が成功したか失敗したかを示す `fmt::Result` を返す。 // `write!` は `println!` と非常によく似た構文を使うことに注意。 write!(f, "{}", self.0) } } }
fmt::Display は fmt::Debug よりも見やすい場合がありますが、 これは std ライブラリに問題をもたらします。曖昧な型はどのように表示されるべきでしょうか? たとえば、std ライブラリがすべての Vec<T> に単一のスタイルを実装した場合、 それはどのようなスタイルであるべきでしょうか?次の 2 つのどちらかでしょうか?
Vec<path>:/:/etc:/home/username:/bin(:で分割)Vec<number>:1,2,3(,で分割)
いいえ。なぜなら、すべての型にとって理想的なスタイルは存在せず、 std ライブラリはそれを決めつけることをしないからです。 fmt::Display は Vec<T> やその他の汎用コンテナには実装されていません。 そのため、これらの汎用的なケースでは fmt::Debug を使う必要があります。
ただし、これは問題ではありません。なぜなら、ジェネリックではない 新しい コンテナ 型であれば、fmt::Display を実装できるからです。
use std::fmt; // `fmt` をインポートする // 2 つの数値を保持する構造体。結果を `Display` と対比できるように、 // `Debug` を derive する。 #[derive(Debug)] struct MinMax(i64, i64); // `MinMax` に対して `Display` を実装する。 impl fmt::Display for MinMax { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 各位置データ点を参照するために `self.number` を使う。 write!(f, "({}, {})", self.0, self.1) } } // 比較のため、フィールドに名前を付けられる構造体を定義する。 #[derive(Debug)] struct Point2D { x: f64, y: f64, } // 同様に、`Point2D` に対して `Display` を実装する。 impl fmt::Display for Point2D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // `x` と `y` だけが示されるようにカスタマイズする。 write!(f, "x: {}, y: {}", self.x, self.y) } } fn main() { let minmax = MinMax(0, 14); println!("Compare structures:"); println!("Display: {}", minmax); println!("Debug: {:?}", minmax); let big_range = MinMax(-300, 300); let small_range = MinMax(-3, 3); println!("The big range is {big} and the small is {small}", small = small_range, big = big_range); let point = Point2D { x: 3.3, y: 7.2 }; println!("Compare points:"); println!("Display: {}", point); println!("Debug: {:?}", point); // 次の行はコンパイルできない。`Debug` と `Display` はどちらも // 実装されているが、`{:b}` には `fmt::Binary` が実装されている必要があり、 // `Point2D` には実装されていないためである。 // println!("What does Point2D look like in binary: {:b}?", point); }
つまり、fmt::Display は実装されていますが fmt::Binary は実装されていないため、 使用できません。std::fmt にはこのような traits が数多くあり、 それぞれ独自の実装を必要とします。これについては std::fmt でさらに詳しく説明されています。
演習
上の例の出力を確認したら、Point2D 構造体を参考にして、 例に Complex 構造体を追加してください。同じ方法で出力したとき、 出力は次のようになるはずです。
Display: 3.3 +7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
Display: 4.7 -2.3i
Debug: Complex { real: 4.7, imag: -2.3 }
ボーナス: +/- 記号の後にスペースを追加してください。
行き詰まった場合のヒント: