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::Rangecore::range::RangeFromcore::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 で発見するより良い。

コメント
…