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

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::Displayfmt::Debug よりも見やすい場合がありますが、 これは std ライブラリに問題をもたらします。曖昧な型はどのように表示されるべきでしょうか? たとえば、std ライブラリがすべての Vec<T> に単一のスタイルを実装した場合、 それはどのようなスタイルであるべきでしょうか?次の 2 つのどちらかでしょうか?

  • Vec<path>: /:/etc:/home/username:/bin: で分割)
  • Vec<number>: 1,2,3, で分割)

いいえ。なぜなら、すべての型にとって理想的なスタイルは存在せず、 std ライブラリはそれを決めつけることをしないからです。 fmt::DisplayVec<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 }

ボーナス: +/- 記号の後にスペースを追加してください。

行き詰まった場合のヒント:

  • std::fmtSign/#/0 のドキュメントを確認してください。
  • ボーナス: if-else 分岐と abs 関数を確認してください。

関連項目:

derivestd::fmtmacrosstructtrait、および use