@@ -57,6 +57,26 @@ This `struct` definition is maximally consistent with the existing `Range`.
57
57
` a..b ` and ` a...b ` are the same size and have the same fields, just with
58
58
the expected difference in semantics.
59
59
60
+ The range ` a...b ` contains all ` x ` where ` a <= x && x <= b ` . As such, an
61
+ inclusive range is non-empty _ iff_ ` a <= b ` . When the range is iterable,
62
+ a non-empty range will produce at least one item when iterated. Because
63
+ ` T::MAX...T::MAX ` is a non-empty range, the iteration needs extra handling
64
+ compared to a half-open ` Range ` . As such, ` .next() ` on an empty range
65
+ ` y...y ` will produce the value ` y ` and replace the range with the canonical
66
+ empty range for the type. Using methods on the the existing (but unstable)
67
+ [ ` Step ` trait] [ step_trait ] , that's ` 1...0 ` for all currently-iterable
68
+ value types. Providing such a range is not a burden on the ` T ` type as
69
+ any such range is acceptable, and only ` PartialOrd ` is required so
70
+ it can be satisfied with an incomparable value ` n ` with ` !(n <= n) ` .
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 that providing a pair ` (x, y) ` such that ` !(x <= y) ` .
77
+
78
+ [ step_trait ] : https://github.com/rust-lang/rust/issues/27741
79
+
60
80
# Drawbacks
61
81
62
82
There's a mismatch between pattern-` ... ` and expression-` ... ` , in that
@@ -66,32 +86,9 @@ semantically.)
66
86
67
87
The ` ... ` vs. ` .. ` distinction is the exact inversion of Ruby's syntax.
68
88
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
89
+ This proposal makes the post-iteration values of the ` start ` and ` end ` fields
90
+ constant, and thus useless. Some of the alternatives would expose the
91
+ last value returned from the iteration, through a more complex interface.
95
92
96
93
# Alternatives
97
94
@@ -110,20 +107,30 @@ reevaluated for usefulness and conflicts with other proposed syntax.
110
107
field is set once the ends match. But having the extra field in a
111
108
language-level desugaring, catering to one library use-case is a little
112
109
non-"hygienic". It is especially strange that the field isn't consistent
113
- across the different ` ... ` desugarings.
110
+ across the different ` ... ` desugarings. And the presence of the public
111
+ field encourages checkinging it, which can be misleading as
112
+ ` r.finished == false ` does not guarantee that ` r.count() > 0 ` .
114
113
- ` 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 .
114
+ This is cleaner than the ` finished ` field, but still has the problem that
115
+ there's no invariant maintained: while an ` Empty ` range is definitely empty,
116
+ a ` NonEmpty ` range might actually be empty. And requiring matching on every
117
+ use of the type is less ergonomic . For example, the clamp RFC would
118
+ naturally use a ` RangeInclusive ` parameter , but because it still needs
119
+ to ` assert!(start <= end) ` in the ` NonEmpty ` arm, the noise of the ` Empty `
120
+ vs ` NonEmpty ` match provides it no value .
122
121
- ` a...b ` only implements ` IntoIterator ` , not ` Iterator ` , by
123
122
converting to a different type that does have the field. However,
124
123
this means that ` a.. .b ` behaves differently to ` a..b ` , so
125
124
` (a...b).map(|x| ...) ` doesn't work (the ` .. ` version of that is
126
125
used reasonably often, in the author's experience)
126
+ - Different choices for the end of iteration are also possible. While neither
127
+ ` start ` nor ` end ` can always have the last value of the iteration while
128
+ still producing an empty range (consider what happens with ` MIN...MIN `
129
+ and ` MAX...MAX ` ), they could be closer. For example, ` a...a ` could become
130
+ ` (a+1)...a ` where possible, and ` a...(a-1) ` otherwise.
131
+ - The name of the ` end ` field could be different, perhaps ` last ` , to reflect
132
+ its different (inclusive) semantics from the ` end ` (exclusive) field on
133
+ the other ranges.
127
134
128
135
# Unresolved questions
129
136
0 commit comments