@@ -27,21 +27,17 @@ 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 `
@@ -57,6 +53,10 @@ now would be `1. ..` i.e. a floating point number on the left,
57
53
however, fortunately, it is actually tokenised like ` 1 ... ` , and is
58
54
hence an error with the current compiler.
59
55
56
+ This ` struct ` definition is maximally consistent with the existing ` Range ` .
57
+ ` a..b ` and ` a...b ` are the same size and have the same fields, just with
58
+ the expected difference in semantics.
59
+
60
60
# Drawbacks
61
61
62
62
There's a mismatch between pattern-` ... ` and expression-` ... ` , in that
@@ -66,10 +66,32 @@ semantically.)
66
66
67
67
The ` ... ` vs. ` .. ` distinction is the exact inversion of Ruby's syntax.
68
68
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.
69
+ Not having a separate marker for ` finished ` or ` empty ` implies a requirement
70
+ on ` T ` that it's possible to provide values such that ` b...a ` is an empty
71
+ range. But a separate marker is a false invariant: whether a ` finished `
72
+ field on the struct or a ` Empty ` variant of an enum, the range ` 10...0 ` still
73
+ desugars to a ` RangeInclusive ` with ` finised: false ` or of the ` NonEmpty `
74
+ variant. And the fields are public, so even fixing the desugar cannot
75
+ guarantee the invariant. As a result, all code using a ` RangeInclusive `
76
+ must still check whether a "` NonEmpty ` " or "un` finished ` " is actually finished.
77
+ The "can produce an empty range" requirement is not a hardship. It's trivial
78
+ for anything that can be stepped forward and backward, as all things which are
79
+ iterable in ` std ` are today. But ther are other possibilities as well. The
80
+ proof-of-concept implementation for this change is done using the ` replace_one `
81
+ and ` replace_zero ` methods of the (existing but unstable) ` Step ` trait, as
82
+ ` 1...0 ` is of course an empty range. Something weirder, like walking along a
83
+ DAG, could use the fact that ` PartialOrd ` is sufficient, and produce a range
84
+ similar in character to ` NaN...NaN ` , which is empty as ` (NaN <= NaN) == false ` .
85
+ The exact details of what is required to make a range iterable is outside the
86
+ scope of this RFC, and will be decided in the [ ` step_by ` issue] [ step_by ] .
87
+
88
+ Note that iterable ranges today have a much stronger bound than just
89
+ steppability: they require a ` b is-reachable-from a ` predicate (as ` a <= b ` ).
90
+ Providing that efficiently for a DAG walk, or even a simpler forward list
91
+ walk, is a substantially harder thing to do that providing a pair ` (x, y) `
92
+ such that ` !(x <= y) ` .
93
+
94
+ [ step_by ] : https://github.com/rust-lang/rust/issues/27741
73
95
74
96
# Alternatives
75
97
@@ -83,28 +105,25 @@ This RFC proposes single-ended syntax with only an end, `...b`, but not
83
105
with only a start (` a... ` ) or unconstrained ` ... ` . This balance could be
84
106
reevaluated for usefulness and conflicts with other proposed syntax.
85
107
86
- The ` Empty ` variant could be omitted, leaving two options:
87
-
88
108
- ` RangeInclusive ` could be a struct including a ` finished ` field.
109
+ This makes it easier for the struct to always be iterable, as the extra
110
+ field is set once the ends match. But having the extra field in a
111
+ language-level desugaring, catering to one library use-case is a little
112
+ non-"hygienic". It is especially strange that the field isn't consistent
113
+ across the different ` ... ` desugarings.
114
+ - ` RangeInclusive ` could be an enum with ` Empty ` and ` NonEmpty ` variants.
115
+ This is cleaner than the ` finished ` field, but makes all uses of the
116
+ type substantially more complex. For example, the clamp RFC would
117
+ naturally use a ` RangeInclusive ` parameter, but then the
118
+ unreliable-` Empty ` vs ` NonEmpty ` distinction provides no value. It does
119
+ prevent looking at ` start ` after iteration has completed, but that is
120
+ of questionable value when ` Range ` allows it without issue, and disallowing
121
+ looking at ` start ` while allowing looking at ` end ` feels inconsistent.
89
122
- ` a...b ` only implements ` IntoIterator ` , not ` Iterator ` , by
90
123
converting to a different type that does have the field. However,
91
124
this means that ` a.. .b ` behaves differently to ` a..b ` , so
92
125
` (a...b).map(|x| ...) ` doesn't work (the ` .. ` version of that is
93
126
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).
108
127
109
128
# Unresolved questions
110
129
@@ -114,3 +133,5 @@ None so far.
114
133
115
134
* In rust-lang/rfcs #1320 , this RFC was amended to change the ` RangeInclusive `
116
135
type from a struct with a ` finished ` field to an enum.
136
+ * In rust-lang/rfcs#TODO, this RFC was amended to change the ` RangeInclusive `
137
+ type from an enum to a struct with just ` start ` and ` end ` fields.
0 commit comments