Progrust Library.

// dictionary

Displayトレイト.

Displayは、値を利用者向けに表示するための標準ライブラリのトレイトです(std::fmt::Display)。コンソール出力の{}のうち、?やxのような型の指定子を付けないもの({:>10}のように幅や寄せだけを指定したものを含む)は、このトレイトの実装を呼び出します1。

要求されるメソッドはfmtの1つだけで、シグネチャはDebugトレイトと同じです。ただしDebugと違って、標準ライブラリはDisplayのderive属性を用意していません。導出できないため、手で実装します2。そのかわり、実装すると文字列型を返すto_stringが自動で使えるようになります3。

use std::fmt;

struct Book {
    title: String,
    price: u32,
}

impl fmt::Display for Book {
    // 表示したい形を自分で決めて、フォーマッタに書き込む
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "『{}』{}円", self.title, self.price)
    }
}

fn main() {
    let book = Book {
        title: String::from("Rust入門"),
        price: 3200,
    };
    println!("{}", book); // 『Rust入門』3200円

    // Displayを実装すると to_string も使えるようになる
    let label: String = book.to_string();
    println!("ラベル: {}", label); // ラベル: 『Rust入門』3200円
}
Playgroundで開く

Debugとの違いとderiveできない理由

Debugは「型名・フィールド名・値を機械的に並べる」という決まった形があるため導出できますが、Displayが担うのは利用者に見せる表示であり、何をどう見せるかは型ごとの意味に依存します。そのため標準ライブラリは導出を用意しておらず、std公式ドキュメントも「Displayは利用者向けの出力のためのものなので導出できない」と明記しています2。素の状態で#[derive(Display)]と書いても、そのようなderiveマクロは存在しないというエラーになります(Rust 1.93で確認。外部クレートが同名のderiveマクロを提供している場合は、それを取り込めば書けます)。

観点 Debug Display
書式指定 {:?}・{:#?} {}(型の指定子なし)
想定する読み手 開発者 利用者
deriveでの導出 できる(#[derive(Debug)]) 標準ライブラリには無い(手で実装)
実装の方針 すべての公開型に実装することが推奨4 すべての型が実装することは想定されていない4
出力の性質 内部状態をできるだけ忠実に表す4 情報を省いてよく、解析できるとは限らない2

ToStringが自動で付いてくる

Displayを実装した型には、to_stringメソッドが自動的に備わります。標準ライブラリにimpl<T: Display + ?Sized> ToString for Tというブランケット実装(ジェネリクスの型引数にトレイト境界を付けて、条件を満たす全型へまとめて実装するもの)があるためです3。

そのためToStringを自分で実装する必要はなく、公式ドキュメントは「ToStringを直接実装すべきではない。かわりにDisplayを実装すれば、ToStringの実装は無償で手に入る」と明記しています3。format!("{}", book)とbook.to_string()は、どちらも同じ文字列を返します。

補足

Displayを実装していない型を表示しようとすると

Displayを実装していない型を{}で表示しようとすると、コンパイルエラー(E0277)になります。Debugさえ実装していれば{:?}では表示できるため、開発中の確認が目的なら{:?}に変え、利用者に見せる表示が必要ならDisplayを実装します。

struct Book {
    title: String,
    price: u32,
}

fn main() {
    let book = Book {
        title: String::from("Rust入門"),
        price: 3200,
    };
    println!("{}", book); // エラー: E0277(BookはDisplayを実装していない)
}
幅や寄せの指定が効かないとき

{:>10}のように呼び出し側が付けた幅・寄せ・精度の指定は、Display実装の中でwrite!をそのまま使うと無視されます。write!は組み立てた文字列をFormatter::write_strで書き込むだけのマクロで、フォーマッタが保持している呼び出し側の設定を参照しないためです6(write!自身の書式文字列に書いた{:>8}などの指定は、これとは別に解釈されます)。

呼び出し側の指定を反映させたい場合は、組み立てた文字列をFormatter::padに渡します。padは幅(最小の文字数)・寄せ・精度(最大の文字数。超えた分は切り詰め)を解釈します6。

use std::fmt;

struct Yen(u32);
struct PaddedYen(u32);

impl fmt::Display for Yen {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}円", self.0) // 呼び出し側の幅指定は無視される
    }
}

impl fmt::Display for PaddedYen {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad(&format!("{}円", self.0)) // 呼び出し側の幅・寄せ・精度が反映される
    }
}

fn main() {
    println!("[{:>10}]", Yen(1200));       // [1200円](指定が無視される)
    println!("[{:>10}]", PaddedYen(1200)); // [     1200円](幅と寄せが効く)
    println!("[{:.2}]", PaddedYen(1200));  // [12](精度は最大文字数として効く)
}
Playgroundで開く
Displayの実装が要求される場面
場面 要求される理由
{}を含むprintln!・format!など 型の指定子を付けない{}がDisplayの実装を呼ぶため1
to_string ToStringのブランケット実装がT: Displayを要求するため3
std::error::Errorの実装 Errorの定義自体がDebug + Displayを要求するため7

自作のエラー型をErrorとして扱えるようにするには、Debug(derive属性でよい)とDisplay(手で実装)の両方が必要です7。「エラー型を作ったのにErrorが実装できない」ときは、Displayの実装忘れがよくある原因です。

表示の形を複数持ちたいとき

1つの型にDisplay実装は1つしか持てないため、同じ型を別の形でも表示したい場合、std公式ドキュメントは表示アダプタ(display adapter)という方法を勧めています5。Displayを実装したラッパー型を返すメソッドを用意する形で、標準ライブラリのstr::escape_defaultやPath::displayが実際にこれにあたります5。

Footnotes

  1. std公式ドキュメント — Module std::fmt 書式指定とトレイトの対応表に “nothing ⇒ Display” とある。“If no format is specified (as in {} or {:6}), then the format trait used is the Display trait.” ↩ ↩2

  2. std公式ドキュメント — Trait Display “Format trait for an empty format, {}.” / “Display is similar to Debug, but Display is for user-facing output, and so cannot be derived.” / “Display for a type might not necessarily be a lossless or complete representation of the type. It may omit internal state, precision, or other information the type does not consider important for user-facing output… As such, the output of Display might not be possible to parse.” ↩ ↩2 ↩3

  3. std公式ドキュメント — Trait ToString “This trait is automatically implemented for any type which implements the Display trait. As such, ToString shouldn’t be implemented directly: Display should be implemented instead, and you get the ToString implementation for free.” ブランケット実装は impl<T> ToString for T where T: Display + ?Sized。 ↩ ↩2 ↩3 ↩4

  4. std公式ドキュメント — Module std::fmt(fmt::Display vs fmt::Debug) “fmt::Display implementations assert that the type can be faithfully represented as a UTF-8 string at all times. It is not expected that all types implement the Display trait.” / “fmt::Debug implementations should be implemented for all public types. Output will typically represent the internal state as faithfully as possible.” ↩ ↩2 ↩3

  5. std公式ドキュメント — Trait Display(Internationalization) “Because a type can only have one Display implementation, it is often preferable to only implement Display when there is a single most ‘obvious’ way that values can be formatted as text.” / “the most flexible approach is display adapters: methods like str::escape_default or Path::display which create a wrapper implementing Display to output the specific display format.” ↩ ↩2 ↩3

  6. std公式ドキュメント — Formatter::pad “Takes a string slice and emits it to the internal buffer after applying the relevant formatting flags specified.” 認識されるフラグは “width - the minimum width of what to emit” / “fill/align” / “precision - the maximum length to emit, the string is truncated if it is longer than this length”。対するFormatter::write_str(write!が経由するメソッド)の例では “assert_eq!(format!("{Foo:0>8}"), "Foo");” と、呼び出し側の幅指定が反映されないことが示されている。 ↩ ↩2

  7. std公式ドキュメント — Trait Error 定義は pub trait Error: Debug + Display。“Errors must describe themselves through the Display and Debug traits.” / “Implementing the Error trait only requires that Debug and Display are implemented too.” ↩ ↩2

この辞書が使われているページ

backlinks 5

  1. 辞書Debugトレイト
  2. 辞書derive属性
  3. 本動かして学ぶRustプログラミング問題集 › 第19章 derive属性と標準トレイト
  4. 本動かして学ぶRustプログラミング問題集 › 第20章 トレイト境界とimpl Trait
  5. 本動かして学ぶRustプログラミング問題集 › 第22章 ライフタイム