🦀

委譲による Display 実装

2023/01/04に公開

はじめに

Rustでユーザ定義型を println などできるようにするには Display トレイトを実装します。この実装は例えば Display のドキュメント にある通り以下のようになります。

use std::fmt;

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

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

let origin = Point { x: 0, y: 0 };

assert_eq!(format!("The origin is: {origin}"), "The origin is: (0, 0)");

ライブラリドキュメントだけでなく、Rust By ExampleDisplay の実装について紹介した記事でもたいていこの形式です。

これは確かに機能するのですが、ちょっと問題があります。
例えば幅10文字の右寄せで表示したいと

println!("{origin:>10}");

としても

(0, 0)

となって右寄せにはなりません。
実は fmt 関数の引数である fmt::Formatter{:>10} といった書式に関する情報を保持しています。そのため本来は fmt の実装者がそれらの書式情報を見て細かく実装しなければならないのです。とはいえ毎回それをやるのは面倒なので、ここでは委譲により楽に書式対応する方法を紹介します。

委譲による Display 実装

委譲による実装は簡単で、単に write! を呼ぶ代わりに一旦表示したい文字列を生成して、その文字列の fmt を呼ぶだけです。

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

これで

println!("{origin:>10}");

    (0, 0)

となりました。
ちなみに、一旦文字列化しているのでメモリアロケーションは元の方式より1回余分に発生します。もし大量に表示したくてここのパフォーマンスが気になる場合は真面目に実装しましょう。

同様に数値を表示したい場合は、数値を作ってその数値に委譲するといいでしょう。

struct X(i32);

impl fmt::Display for X {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

struct Y(f32);

impl fmt::Display for Y {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

これで桁数指定や先頭の0埋めなど数値が対応している書式指定全てに対応することができます。

Discussion