@@ -27,26 +27,21 @@ more dots means more elements.
2727
2828``` rust
2929pub enum RangeInclusive <T > {
30- Empty {
31- at : T ,
32- },
33- NonEmpty {
34- start : T ,
35- end : T ,
36- }
30+ pub start : T ,
31+ pub end : T ,
3732}
3833
3934pub struct RangeToInclusive <T > {
4035 pub end : T ,
4136}
4237```
4338
44- Writing ` a...b ` in an expression desugars to ` std::ops::RangeInclusive::NonEmpty { start: a, end: b } ` . Writing ` ...b ` in an
39+ Writing ` a...b ` in an expression desugars to
40+ ` std::ops::RangeInclusive { start: a, end: b } ` . Writing ` ...b ` in an
4541expression desugars to ` std::ops::RangeToInclusive { end: b } ` .
4642
4743` RangeInclusive ` implements the standard traits (` Clone ` , ` Debug `
48- etc.), and implements ` Iterator ` . The ` Empty ` variant is to allow the
49- ` Iterator ` implementation to work without hacks (see Alternatives).
44+ etc.), and implements ` Iterator ` .
5045
5146The use of ` ... ` in a pattern remains as testing for inclusion
5247within that range, * not* a struct match.
@@ -57,6 +52,42 @@ now would be `1. ..` i.e. a floating point number on the left,
5752however, fortunately, it is actually tokenised like ` 1 ... ` , and is
5853hence an error with the current compiler.
5954
55+ This ` struct ` definition is maximally consistent with the existing ` Range ` .
56+ ` a..b ` and ` a...b ` are the same size and have the same fields, just with
57+ the expected difference in semantics.
58+
59+ The range ` a...b ` contains all ` x ` where ` a <= x && x <= b ` . As such, an
60+ inclusive range is non-empty _ iff_ ` a <= b ` . When the range is iterable,
61+ a non-empty range will produce at least one item when iterated. Because
62+ ` T::MAX...T::MAX ` is a non-empty range, the iteration needs extra handling
63+ compared to a half-open ` Range ` . As such, ` .next() ` on an empty range
64+ ` y...y ` will produce the value ` y ` and adjust the range such that
65+ ` !(start <= end) ` . Providing such a range is not a burden on the ` T ` type as
66+ any such range is acceptable, and only ` PartialOrd ` is required so
67+ it can be satisfied with an incomparable value ` n ` with ` !(n <= n) ` .
68+ A caller must not, in general, expect any particular ` start ` or ` end `
69+ after iterating, and is encouraged to detect empty ranges with
70+ ` ExactSizeIterator::is_empty ` instead of by observing fields directly.
71+
72+ Note that because ranges are not required to be well-formed, they have a
73+ much stronger bound than just needing successor function: they require a
74+ ` b is-reachable-from a ` predicate (as ` a <= b ` ). Providing that efficiently
75+ for a DAG walk, or even a simpler forward list walk, is a substantially
76+ harder thing to do than providing a pair ` (x, y) ` such that ` !(x <= y) ` .
77+
78+ Implementation note: For currently-iterable types, the initial implementation
79+ of this will have the range become ` 1...0 ` after yielding the final value,
80+ as that can be done using the ` replace_one ` and ` replace_zero ` methods on
81+ the existing (but unstable) [ ` Step ` trait] [ step_trait ] . It's expected,
82+ however, that the trait will change to allow more type-appropriate ` impl ` s.
83+ For example, a ` num::BitInt ` may rather become empty by incrementing ` start ` ,
84+ as ` Range ` does, since it doesn't to need to worry about overflow. Even for
85+ primitives, it could be advantageous to choose a different implementation,
86+ perhaps using ` .overflowing_add(1) ` and swapping on overflow, or ` a...a `
87+ could become ` (a+1)...a ` where possible and ` a...(a-1) ` otherwise.
88+
89+ [ step_trait ] : https://github.com/rust-lang/rust/issues/27741
90+
6091# Drawbacks
6192
6293There's a mismatch between pattern-` ... ` and expression-` ... ` , in that
@@ -66,10 +97,9 @@ semantically.)
6697
6798The ` ... ` vs. ` .. ` distinction is the exact inversion of Ruby's syntax.
6899
69- Having an extra field in a language-level desugaring, catering to one
70- library use-case is a little non-"hygienic". It is especially strange
71- that the field isn't consistent across the different ` ... `
72- desugarings.
100+ This proposal makes the post-iteration values of the ` start ` and ` end ` fields
101+ constant, and thus useless. Some of the alternatives would expose the
102+ last value returned from the iteration, through a more complex interface.
73103
74104# Alternatives
75105
@@ -83,28 +113,30 @@ This RFC proposes single-ended syntax with only an end, `...b`, but not
83113with only a start (` a... ` ) or unconstrained ` ... ` . This balance could be
84114reevaluated for usefulness and conflicts with other proposed syntax.
85115
86- The ` Empty ` variant could be omitted, leaving two options:
87-
88116- ` RangeInclusive ` could be a struct including a ` finished ` field.
117+ This makes it easier for the struct to always be iterable, as the extra
118+ field is set once the ends match. But having the extra field in a
119+ language-level desugaring, catering to one library use-case is a little
120+ non-"hygienic". It is especially strange that the field isn't consistent
121+ across the different ` ... ` desugarings. And the presence of the public
122+ field encourages checkinging it, which can be misleading as
123+ ` r.finished == false ` does not guarantee that ` r.count() > 0 ` .
124+ - ` RangeInclusive ` could be an enum with ` Empty ` and ` NonEmpty ` variants.
125+ This is cleaner than the ` finished ` field, but still has the problem that
126+ there's no invariant maintained: while an ` Empty ` range is definitely empty,
127+ a ` NonEmpty ` range might actually be empty. And requiring matching on every
128+ use of the type is less ergonomic. For example, the clamp RFC would
129+ naturally use a ` RangeInclusive ` parameter, but because it still needs
130+ to ` assert!(start <= end) ` in the ` NonEmpty ` arm, the noise of the ` Empty `
131+ vs ` NonEmpty ` match provides it no value.
89132- ` a...b ` only implements ` IntoIterator ` , not ` Iterator ` , by
90133 converting to a different type that does have the field. However,
91134 this means that ` a.. .b ` behaves differently to ` a..b ` , so
92135 ` (a...b).map(|x| ...) ` doesn't work (the ` .. ` version of that is
93136 used reasonably often, in the author's experience)
94- - ` a...b ` can implement ` Iterator ` for types that can be stepped
95- backwards: the only case that is problematic things cases like
96- ` x...255u8 ` where the endpoint is the last value in the type's
97- range. A naive implementation that just steps ` x ` and compares
98- against the second value will never terminate: it will yield 254
99- (final state: ` 255...255 ` ), 255 (final state: ` 0...255 ` ), 0 (final
100- state: ` 1...255 ` ). I.e. it will wrap around because it has no way to
101- detect whether 255 has been yielded or not. However, implementations
102- of ` Iterator ` can detect cases like that, and, after yielding ` 255 ` ,
103- backwards-step the second piece of state to ` 255...254 ` .
104-
105- This means that ` a...b ` can only implement ` Iterator ` for types that
106- can be stepped backwards, which isn't always guaranteed, e.g. types
107- might not have a unique predecessor (walking along a DAG).
137+ - The name of the ` end ` field could be different, perhaps ` last ` , to reflect
138+ its different (inclusive) semantics from the ` end ` (exclusive) field on
139+ the other ranges.
108140
109141# Unresolved questions
110142
@@ -114,3 +146,5 @@ None so far.
114146
115147* In rust-lang/rfcs #1320 , this RFC was amended to change the ` RangeInclusive `
116148 type from a struct with a ` finished ` field to an enum.
149+ * In rust-lang/rfcs #1980 , this RFC was amended to change the ` RangeInclusive `
150+ type from an enum to a struct with just ` start ` and ` end ` fields.
0 commit comments