Rust 1.96 の Range 型:Copy を Iterator から取り戻す

Range はそれ自身が Iterator を実装しているので Copy になれなかった。Rust 1.96 は IntoIterator だけを実装する新しい Range 型群を安定化し、Copy にした。この区別がなぜ重要か、そして慣れた構文がいつ変わるか。

Rust 1.96.0(2026 年 5 月 28 日)は、小さく見えて多くのコードにじわじわ染み込むものを安定化しました。新しい Range 型です。

背景にある不満は一行です。Range は Copy であるべきなのに、そうなっていない。

なぜそうなっていないか

Range が自ら Iterator を実装しているからです。Iterator と Copy を同時に実装する型は落とし穴になります。Copy は「代入後も元が使える」という意味なので、for x in r の後で r はまだそこにあります —— ただし今しがた消費したばかりです。もう一度繰り返すとは何を意味するのか。空か、先頭からか。意味論は即座に崩れます。

そこで標準ライブラリは Range に Copy を与えず、代価として都合よく保存して再利用できないままにしました。スライスの一部分を構造体に持つには、フィールドを二つに割るしかありません。

struct Span {
    start: usize,
    end: usize,
}

馬鹿げていますが、他に手がありませんでした。

RFC 3550 の答え:IntoIterator に変える

新たに安定化された型は core::range の下にあります。

  • core::range::Range
  • core::range::RangeFrom
  • core::range::RangeInclusive

違いはただ一つ。IntoIterator を実装し、Iterator は実装しない。すると Copy は落とし穴でなくなります。Range を複製しても「どこまで反復したか」は複製されない。その状態が型に存在しないからです。

つまり先ほどの構造体はこう書けます。

use core::range::Range;

#[derive(Clone, Copy)]
pub struct Span(Range<usize>);

impl Span {
    pub fn of(self, s: &str) -> &str {
        &s[self.0]
    }
}

もう一つ細かい点。新しい RangeInclusive はフィールドを公開します。旧型が隠していたのは「反復し尽くした」という内部状態を秘匿するためでした。新型にその状態は無いので、隠す必要もありません。

構文はまだ切り替わっていない

今 0..1 と書いて得られるのは依然として旧 core::ops の型です。公式の説明は「将来の edition で range 構文は core::range 型に更新される」というものです。

したがって:

  • 今移行するなら core::range::Range { start, end } と明示するか変換が必要
  • 既存の 0..1 の挙動は変わらず、突然コンパイルエラーにはならない
  • ただし新規コードで将来の書き換えを減らしたいなら、今から新しい型を使える

今後追加されるのは core::range::RangeFull と core::range::RangeTo(もともと Iterator を実装せず Copy だったので再エクスポートにすぎません)、そして現在の型群の新しい住処としての core::range::legacy::* です。

私の結論

これは差分が小さく影響が遅い種類の安定化です。今日多くの行数を節約できるわけではありませんが、長く続いた設計上の妥協を修理します。

「反復できる」と「複製できる」は型のレベルで矛盾しており、標準ライブラリは両方を持たないことを選び、利用者はフィールドを割ることを強いられていました。新しい型は関心を分離します。反復可能(IntoIterator)と複製可能(Copy)は、そもそも溶接されるべきではなかったのです。

注目すべき信号は構文がいつ切り替わるかです。その日、0..1 の型が変わり、「Range は Iterator」に依存するコード —— たとえば r をそのまま impl Iterator を取る関数に渡している箇所 —— が壊れ始めます。事前に知っておくほうが、CI で発見するより良い。

← 記事一覧に戻る

コメント

…