// 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
-
std公式ドキュメント — Module std::fmt 書式指定とトレイトの対応表に “nothing ⇒
Display” とある。“If no format is specified (as in{}or{:6}), then the format trait used is theDisplaytrait.” ↩ ↩2 -
std公式ドキュメント — Trait Display “Format trait for an empty format,
{}.” / “Displayis similar toDebug, butDisplayis for user-facing output, and so cannot be derived.” / “Displayfor 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 ofDisplaymight not be possible to parse.” ↩ ↩2 ↩3 -
std公式ドキュメント — Trait ToString “This trait is automatically implemented for any type which implements the
Displaytrait. As such,ToStringshouldn’t be implemented directly:Displayshould be implemented instead, and you get theToStringimplementation for free.” ブランケット実装はimpl<T> ToString for T where T: Display + ?Sized。 ↩ ↩2 ↩3 ↩4 -
std公式ドキュメント — Module std::fmt(fmt::Display vs fmt::Debug) “
fmt::Displayimplementations assert that the type can be faithfully represented as a UTF-8 string at all times. It is not expected that all types implement theDisplaytrait.” / “fmt::Debugimplementations should be implemented for all public types. Output will typically represent the internal state as faithfully as possible.” ↩ ↩2 ↩3 -
std公式ドキュメント — Trait Display(Internationalization) “Because a type can only have one
Displayimplementation, it is often preferable to only implementDisplaywhen there is a single most ‘obvious’ way that values can be formatted as text.” / “the most flexible approach is display adapters: methods likestr::escape_defaultorPath::displaywhich create a wrapper implementingDisplayto output the specific display format.” ↩ ↩2 ↩3 -
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 -
std公式ドキュメント — Trait Error 定義は
pub trait Error: Debug + Display。“Errors must describe themselves through theDisplayandDebugtraits.” / “Implementing theErrortrait only requires thatDebugandDisplayare implemented too.” ↩ ↩2
この辞書が使われているページ
backlinks 5