LoginSignup
4
2

More than 3 years have passed since last update.

RustでDisplayトレイトを実装する際に与えられたフォーマット指定を使う方法

Posted at

概要

構造体などのフィールドをフォーマットするときに、println!マクロなどで与えられたフォーマット指定をそのまま使ってフォーマットしたいという状況にあるとします。このとき、フィールドのDisplayトレイトのfmtメソッドにFormatterを渡して呼び出すことで、与えられたフォーマット指定を使って値をフォーマットすることができます。つまり、次のような形で実装します。

impl fmt::Display for MyData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.field.fmt(f)
    }
}

はじめに

数値型をフィールドに持つ構造体にDisplayトレイトを実装するときに、パディングや精度などのフォーマット指定を受け付けられるようにしたいと思います。これを実現するためには、フィールドのfmtメソッドを呼び出せば良いということを、具体例を使って紹介します。

Displayトレイトの実装例はRust By Example 日本語版でも紹介されていますが1、これらの例はwrite!などのマクロを呼び出す際に、Displayトレイトの実装の中でフォーマット文字列を新たに指定しているため、自ら実装したfmtメソッドに与えられたフォーマット指定は無視されてしまいます。

表題の目的を実現する方法について書いている記事は、少なくとも日本語では見当たらなかったのでこの記事を書くことにしました。なお、この記事のソースコードはRust 1.48.0で動作を確認しました。

平面上の点を表現する構造体Pointを定義したとしましょう。

struct Point {
    x: f64,
    y: f64,
}

これにDisplayトレイトを実装して、次のコードが通るようにしたいのです。

    let pt = Point { x: 0.5, y: (3.0_f64).sqrt()/2.0 };

    assert_eq!(format!("{}", pt),                "(0.5, 0.8660254037844386)".to_owned());
    assert_eq!(format!("{:.6}", pt),             "(0.500000, 0.866025)".to_owned());
    assert_eq!(format!("{:.prec$}", pt, prec=3), "(0.500, 0.866)".to_owned());
    assert_eq!(format!("{:>8.4}", pt),           "(  0.5000,   0.8660)".to_owned());

まずは素朴にDisplayトレイトを実装してみましょう。

impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

このソースコードを実行してみると以下の結果が得られます。

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.23s
     Running `target/debug/playground`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"(0.5, 0.8660254037844386)"`,
 right: `"(0.500000, 0.866025)"`', src/main.rs:16:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

どうやら2番目のassert_eq!でのフォーマット指定が無視されているようです。どうしてこうなったのでしょう?それは、今実装したDisplayトレイトではwrite!マクロでのフォーマット指定を{}としたので、デフォルトのフォーマット方法で出力されたからです。

write!マクロはWriteトレイトのwrite_fmtメソッドを呼び出します。write_fmtメソッドはstd::fmt::write関数を呼び出します。そして、write関数は新しくFormatterを作り、それを使って値をフォーマットします

今、我々に与えられたFormatterは、Point構造体のfmtメソッドの引数であるfです。これを使うようにDisplayトレイトの実装を修正しましょう。各フィールドのfmtメソッドにfを渡して呼べばOKです。少し冗長ですが、修正した実装は以下のようになります。

impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "(")?;
        self.x.fmt(f)?;
        write!(f, ", ")?;
        self.y.fmt(f)?;
        write!(f, ")")
    }
}

このソースコードを実行してみましょう。パニックせずに終了します。これで、自分で実装したDisplayトレイトで与えられたフォーマット指定を使うことができました。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2