chrono::NaiveDate::iter_daysでの注意
はじめに
この記事は Xbit Advent Calendar 2022 の4日目の記事です。
株式会社クロスビット | Xbit, Inc.
https://x-bit.co.jp/
主張
Rustで時間を扱うデファクトスタンダードなライブラリ(crate)として知られるchrono
において
NaiveDate::iter_days(&self)
で得られる日付(NaiveDate)のIteratorに対して
take().rev()
を呼び出した際に想定と異なる挙動をします。
iter_weeks(&self)
についても同様です。
Code
use chrono::NaiveDate;
use itertools::*;
fn main() {
println!("{}", (25..).take(3).join(" ")); // 25 26 27
// println!("{}", (25..).take(3).rev().join(" ")); // Unable to call rev()
// println!("{}", (25..).rev().take(3).join(" ")); // Unable to call rev()
println!();
println!("{}", (25..99).take(3).join(" ")); // 25 26 27
println!("{}", (25..99).take(3).rev().join(" ")); // 27 26 25
println!("{}", (25..99).rev().take(3).join(" ")); // 98 97 96
println!();
let xmas = NaiveDate::from_ymd_opt(2022, 12, 25).unwrap();
println!("{}", xmas.iter_days().take(3).join(" ")); // /25 /26 /27
println!("{}", xmas.iter_days().take(3).rev().join(" ")); // ???
println!("{}", xmas.iter_days().rev().take(3).join(" ")); // /25 /24 /23
}
25 26 27
25 26 27
27 26 25
98 97 96
2022-12-25 2022-12-26 2022-12-27
-258099-12-23
2022-12-25 2022-12-24 2022-12-23
対処法
-
take().rev()
しない - 終わり日時を出してそこから
iter_xxxs().rev().take()
する - 一旦Vecなどにcollectする
原因について
根本的な原因は、iter_days()
が無限iteratorであるにもかかわらず、
- DoubleEndedIterator(iteratorに両端が存在することを表すtrait)
- ExactSizeIterator(iteratorの長さが正確にわかることを主張するtrait)
が(両方)実装されていることで、
TakeにDoubleEndedIteratorが実装される条件を満たしている
(=take().rev()
と書ける)ことであるといえます。
impl<I> DoubleEndedIterator for Take<I>
where
I: DoubleEndedIterator + ExactSizeIterator,
比較として他のIteratorを見ると、
iter_days()
と似たIteratorといえるstd::ops::RangeFrom (start..
のようなrange)では
DoubleEndedIteratorもExactSizeIteratorも実装されていません。
実装されているIteratorの例としてはstd::ops::Range (start..end
のようなrange)があり、
Range<整数型>
などに対してDoubleEndedIteratorやExactSizeIteratorが実装されています。
iter_days()
にこれらのtraitが実装されている理由について考えられることとしては、
DoubleEndedIteratorについてはiter_days().rev()
で逆順iteratorが得られるという仕様が"見た目ではわかりやすい"という利点がありますが、DoubleEndedIteratorの主旨には反しているように思えます。
ExactSizeIteratorについては良くわかりません。(前述のようにRangeFromには一切実装されていないことですし)
おわりに
株式会社クロスビットでは私達と一緒にスタッフスケジューリングの最適化システムなどの開発に携わるバックエンドエンジニア、フロントエンドエンジニア、あるいはフルスタックエンジニアを募集しています。
関連リンク
chronoのissue/PRを軽く調べて見つかった関係ありそうな項目を貼っておきます。
- implement Iterator · Issue #152
https://github.com/chronotope/chrono/issues/152- Step in std::iter - Rust(nightly-only) の安定化が必要 といったことも言われている
- Add support for double ended days+weeks iterators · Pull Request #697
https://github.com/chronotope/chrono/pull/697- DoubleEndedIteratorが実装されたPR
- 他のissue間との言及はない
Discussion