Rust 1.96 的 Range 类型:把 Copy 从迭代器手里拿回来

Range 一直不是 Copy,因为它自己实现了 Iterator。Rust 1.96 稳定了一套新的 Range 类型:只实现 IntoIterator,于是可以 Copy。这篇讲这个区分为什么重要,以及它什么时候会改掉你熟悉的语法。

Rust 1.96.0(2026 年 5 月 28 日)稳定了一件看起来很小、但会慢慢渗透到很多代码里的事:新的 Range 类型。

背景是一句抱怨:Range 应该是 Copy,但它不是。

为什么不是

因为 Range 自己实现了 Iterator。而一个类型同时实现 Iterator 和 Copy 是个陷阱:Copy 意味着「赋值之后原来那份还能用」,于是 for x in r 之后 r 还在,但你刚刚把它迭代完了 —— 再迭代一次会得到什么?空?从头?语义立刻说不清。

所以标准库一直没给 Range 加 Copy,代价是它不能被方便地存起来复用。想在一个结构体里存「切片的一段」,只能拆成 start 和 end 两个字段:

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 会改成 core::range 类型」。

也就是说:

  • 现在换过去要显式写 core::range::Range { start, end } 或转换
  • 已有的 0..1 代码行为不变,不会突然编译失败
  • 但如果你现在写新代码、又想将来少改一次,可以直接用新类型

后续还会补 core::range::RangeFull 与 core::range::RangeTo(它们本来就不实现 Iterator、本来就已经是 Copy,所以只是重新导出),以及 core::range::legacy::* 作为现在这套 Range 的新家。

我的判断

这是那种改动很小、影响很慢的稳定化。今天你不会因为它少写多少代码,但它修掉的是一个长期的设计妥协:

「既能当迭代器又能复制」在类型层面就是矛盾的,标准库选择了不做,用户被迫拆字段。新类型把矛盾拆开 —— 可迭代(IntoIterator)和可复制(Copy)本来就不该绑在一起。

真正值得留意的信号是语法什么时候换。那一天 0..1 的类型变了,任何依赖「Range 是 Iterator」的代码(比如把 r 直接传给接受 impl Iterator 的函数)都会开始报错。到那时再改,比现在有个心理准备要好。

← 返回文章列表

评论

…