@@ -27,26 +27,21 @@ more dots means more elements.
27
27
28
28
``` rust
29
29
pub 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 ,
37
32
}
38
33
39
34
pub struct RangeToInclusive <T > {
40
35
pub end : T ,
41
36
}
42
37
```
43
38
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
45
41
expression desugars to ` std::ops::RangeToInclusive { end: b } ` .
46
42
47
43
` 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 ` .
50
45
51
46
The use of ` ... ` in a pattern remains as testing for inclusion
52
47
within that range, * not* a struct match.
@@ -57,6 +52,42 @@ now would be `1. ..` i.e. a floating point number on the left,
57
52
however, fortunately, it is actually tokenised like ` 1 ... ` , and is
58
53
hence an error with the current compiler.
59
54
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
+
60
91
# Drawbacks
61
92
62
93
There's a mismatch between pattern-` ... ` and expression-` ... ` , in that
@@ -66,10 +97,9 @@ semantically.)
66
97
67
98
The ` ... ` vs. ` .. ` distinction is the exact inversion of Ruby's syntax.
68
99
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.
73
103
74
104
# Alternatives
75
105
@@ -83,28 +113,30 @@ This RFC proposes single-ended syntax with only an end, `...b`, but not
83
113
with only a start (` a... ` ) or unconstrained ` ... ` . This balance could be
84
114
reevaluated for usefulness and conflicts with other proposed syntax.
85
115
86
- The ` Empty ` variant could be omitted, leaving two options:
87
-
88
116
- ` 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.
89
132
- ` a...b ` only implements ` IntoIterator ` , not ` Iterator ` , by
90
133
converting to a different type that does have the field. However,
91
134
this means that ` a.. .b ` behaves differently to ` a..b ` , so
92
135
` (a...b).map(|x| ...) ` doesn't work (the ` .. ` version of that is
93
136
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.
108
140
109
141
# Unresolved questions
110
142
@@ -114,3 +146,5 @@ None so far.
114
146
115
147
* In rust-lang/rfcs #1320 , this RFC was amended to change the ` RangeInclusive `
116
148
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