trait bounds were not satisfied

RustのChronoライブラリ(v0.4)で以下のようなエラーが出た

the method `format` exists for struct `DateTime<Tz>`, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
`<Tz as chrono::TimeZone>::Offset: std::fmt::Display`rustcClick for full compiler

エラーが出るのは次のようなコード

fn main() {
    let dt = Local::now();
    println!("{}", dt.format("%Y-%m-%d %H:%M:%S").to_string()); // これはOK
    println!("{}", format_mysql_dt(dt));                        // これはだめ
}

fn format_mysql_dt<Tz: TimeZone>(dt: DateTime<Tz>) -> String {
    dt.format("%Y-%m-%d %H:%M:%S").to_string() // ここでエラーが出る
}

formatメソッドあるけどtrait boundsがnot satisfiedだよ、と言われている、何のこっちゃ。

エラーが出ないようにするには以下のようにする

fn format_mysql_dt<Tz: TimeZone>(dt: DateTime<Tz>) -> String where Tz::Offset: std::fmt::Display{
    dt.format("%Y-%m-%d %H:%M:%S").to_string()
}

これは何なのだ?

Chronoライブラリのコードを読んでみる

まず、Tz::Offsetは以下のように定義されている

pub trait TimeZone: Sized + Clone {

    type Offset: Offset;

同じ名前なので紛らわしいが、TimeZoneの中でchrono::Offset型の型エイリアスchrono::TimeZone:Offsetが定義されている。

次にformat関数の定義を見てみると

impl<Tz: TimeZone> DateTime<Tz>
where
    Tz::Offset: fmt::Display,
{

 // 途中省略

    #[cfg(any(feature = "alloc", feature = "std", test))]
    #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
    #[inline]
    #[must_use]
    pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
        self.format_with_items(StrftimeItems::new(fmt))
    }

確かにformatはDateTimeのメソッドなのだが、Tz::Offsetがstd::fmt::Displayを実装している場合という条件で定義されている。この条件を満たさずにformatを呼ぼうとしてエラーとなったのだ。

つまり型パラメータが、DateTime<Local>DateTime<Utc>のように具体的な実型パラメータであれば(Local::OffsetやUtc::Offsetはstd::fmt::Displayを実装しているので)呼べるが、DateTime<Tz: TimeZone>のような仮型パラメータの場合は、Tz::Offsetがstd::fmt::Displayを実装しているかどうか分からない、だから呼ぶことができない。

これは何が難しいかというと制限されるのはTimeZoneだが、その中のTimeZone::Offsetのトレイト境界に対する条件であること。ライブラリ使う側はTimeZoneの中のOffsetを意識する必要がないので何のことだか分からないというのがある。

根本的にはchronoライブラリ側で

trait TimeZone {
    type Offset: Offset + std::fmt::Display;
}

のようにトレイト境界を指定していれば良さそうな気がする・・・・Chronoライブラリにfmt::Displayを実装してないOffset型は存在しないようだし・・・