RangeFrom, Part 2..: What I think is wrong about the design
RangeFrom, Part 2..: What I think is wrong about the design
RangeFrom,第二部分:我认为该设计存在的问题
The Beginning
开篇
In December 2025, about 8 months before this post, James Munns made a post about how RangeFrom just wraps when it gets to the end. I started with a clippy lint, but it ended up being one of the things my mind would wander to quite often. This has led to me forming quite a few opinions and thoughts about it, which I want to share with the ether in this article. While not a strict pre-requisite for reading this article, the first part in this series of posts will give some historical background: RangeFrom, Part 1..: History and background.
2025 年 12 月,也就是本文发布前约 8 个月,James Munns 写了一篇文章,讨论了 RangeFrom 在到达末尾时如何进行回绕(wrap)。我最初只是想写一个 clippy lint,但这件事最终成了我经常思考的问题。这使我对其形成了不少观点和想法,我想在本文中与大家分享。虽然阅读本文并非必须,但本系列的第一篇文章提供了相关的历史背景:RangeFrom, Part 1..: History and background。
What would you expect from the RangeFrom iterator?
你对 RangeFrom 迭代器有什么期望?
I will list up a few things that I think people would expect from the RangeFrom iterator. Let’s say we have an iterator let mut iter = (n..). Here are some properties that I would expect from such an iterator:
- All values until and including the largest value are yielded by
n... - It will only yield values in the range.
- It will not panic on overflow when overflow-checks are turned off.
- It will be monotonically increasing, if it doesn’t overflow.
- Iteration over various types will work consistently.
我将列出一些我认为人们对 RangeFrom 迭代器所期望的特性。假设我们有一个迭代器 let mut iter = (n..),我期望它具备以下属性:
n..应当产生直到并包括最大值在内的所有值。- 它只应产生范围内的值。
- 当关闭溢出检查(overflow-checks)时,它不应因溢出而 panic。
- 如果不溢出,它应当是单调递增的。
- 对不同类型的迭代应当保持一致。
In the next parts I am going to show how none of these are correct. 在接下来的部分中,我将展示这些期望为何都不成立。
1. All values until and including the largest value is yielded by n..
1. n.. 应当产生直到并包括最大值在内的所有值
Because of an implementation detail where the internal counter is incremented before the value is yielded, enabling overflow-checks means that the iterator overflows before the final value (u8::MAX) is yielded. Thus the final value will be the penultimate value before the overflow:
for i in 253u8.. { println!("{i}"); }
This code will print 253, 254 and then panic. If overflow-checks are not enabled it will thankfully print 255 as well.
由于实现细节中内部计数器是在值被产生之前递增的,因此启用溢出检查意味着迭代器在产生最终值(u8::MAX)之前就会溢出。因此,最终值将是溢出前的倒数第二个值:
for i in 253u8.. { println!("{i}"); }
这段代码会打印 253、254,然后 panic。如果未启用溢出检查,它会幸运地打印出 255。
It should be noted that this has been fixed with the new range types.
for i in std::range::RangeFrom::from(253u8..).into_iter() { println!("{i}"); }
This will print 253, 254, 255 and then panic if overflow-checks are enabled. But with overflow-checks disabled it will work the same as the current RangeFrom type. That is it will run in an infinite loop, which brings us to:
值得注意的是,这个问题已经在新的范围类型中得到了修复。
for i in std::range::RangeFrom::from(253u8..).into_iter() { println!("{i}"); }
如果启用了溢出检查,这段代码会打印 253、254、255 然后 panic。但如果禁用了溢出检查,它的行为将与当前的 RangeFrom 类型相同,即陷入无限循环,这就引出了下一点:
2. It will only yield values in the range.
2. 它只应产生范围内的值。
This brings us to possibly my main issue with the current design. I would expect that the following unreachable statement was unreachable:
let range = 128u8..;
let iter = range.clone();
for i in iter {
if !range.contains(&i) {
unreachable!("Outside of range");
}
}
It is reachable with the RangeFrom iterator for all the integer types (u* and i*). To me this makes little sense as it seems to break what I think is the main idea of a range, specifically that it conceptually is something like [n, +∞].
这引出了我目前对该设计的主要不满。我期望以下 unreachable 语句确实是不可达的:
let range = 128u8..;
let iter = range.clone();
for i in iter {
if !range.contains(&i) {
unreachable!("Outside of range");
}
}
但对于所有整数类型(u* 和 i*),使用 RangeFrom 迭代器时,这段代码是可达的。对我来说这毫无意义,因为它似乎破坏了我认为范围的核心概念,即它在概念上应该是类似 [n, +∞] 的东西。
This means that you need to be careful about not using the RangeFrom iterator as a guard for values unless you ensure you guard against the overflow. One way you can do this is by using n..={Integer}::MAX, to me this is not the range type that I conceptually would reach for first.
这意味着你必须小心,不要将 RangeFrom 迭代器用作值的守卫,除非你确保自己处理了溢出。一种方法是使用 n..={Integer}::MAX,但对我而言,这并不是我概念中首先会想到的范围类型。
3. It will not panic on overflow when overflow-checks are turned off
3. 当关闭溢出检查时,它不应因溢出而 panic
It is only the primitive integer types that work in this way. The rest of the types that implement the Step trait diverge from this. You could argue that it makes sense for types where all bit patterns are well-defined, such as char and std::ascii::Char, where some values are undefined behavior to create. But if you then look at Ipv4Addr and Ipv6Addr which both complete mappings from the underlying integer type, but both of these always panic on overflow.
只有原始整数类型是这样工作的。其余实现 Step trait 的类型则有所不同。你可能会争辩说,对于所有位模式都有明确定义的类型(如 char 和 std::ascii::Char,其中某些值是未定义行为)来说,这样做是有意义的。但如果你看看 Ipv4Addr 和 Ipv6Addr,它们都完成了到底层整数类型的映射,但两者在溢出时都会 panic。
// Always panics with
// library/core/src/iter/range.rs:118:45:
// overflow in `Step::forward`
for i in Ipv4Addr::new(255, 255, 255, 250).. { println!("{i}"); }
To me this seems like wrong behavior. It should be noted that this mostly follows directly from the implementation notes of the Step trait: “If this would overflow the range of values supported by Self, this function is allowed to panic, wrap, or saturate.”
对我来说,这似乎是错误的行为。需要注意的是,这主要直接源于 Step trait 的实现说明:“如果这会导致超出 Self 支持的值范围,该函数允许 panic、回绕或饱和。”
4. It will be monotonically increasing, if it doesn’t overflow
4. 如果不溢出,它应当是单调递增的
If you read the quote in the previous block you may have spotted the word “saturate” which may have made you wonder what type does that? We have only looked at types without any disallowed bit-patterns. But what happens with types that do have that? Well, it depends; the standard library does it in two different ways. Types such as char and std::ascii::Char will always panic when you reach the end and the allowed bit-patterns. Then you have NonZero<u*> which saturates.
如果你读了上一段的引用,可能注意到了“饱和(saturate)”这个词,这可能会让你好奇哪些类型会这样做?我们之前只看了没有禁用位模式的类型。但那些有禁用位模式的类型会怎样呢?这取决于具体情况;标准库以两种不同的方式处理。像 char 和 std::ascii::Char 这样的类型在到达末尾和允许的位模式时总是会 panic。而 NonZero<u*> 则会饱和。
for i in NonZero::new(250u8).unwrap().. { println!("{i}"); }
When this code is run with overflow-checks = true this code will panic with the last value being 254. When this code is run with overflow-checks = false this code will print out: 250, 251, 252, 253, 254, 255, 255, 255… And just continue like that forever. This means that it is not always monotonically increasing since it will stay the same.
当 overflow-checks = true 时运行此代码,它会 panic,最后一个值为 254。当 overflow-checks = false 时,它会打印:250, 251, 252, 253, 254, 255, 255, 255… 并永远持续下去。这意味着它并不总是单调递增的,因为它会保持不变。
5. Iteration over various types should work consistently
5. 对不同类型的迭代应当保持一致
A last thing I want to highlight is that the standard library is a bit inconsistent with how it works. There are 7 different types (I count signed and unsigned integers as one type each). They each work in one of 3 different ways. The overflow behavior of the Step implementation of various types in…
最后我想强调的是,标准库在工作方式上有些不一致。共有 7 种不同的类型(我将有符号和无符号整数各算作一种类型)。它们各自以 3 种不同的方式之一工作。各种类型的 Step 实现的溢出行为在……