From 8f91f8ad5377226d89b1e36c6ab57ce5d15bf7e6 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Tue, 7 Jul 2015 11:47:25 -0700 Subject: [PATCH 1/3] RFC for inclusive ranges with ... --- text/0000-inclusive-ranges.md | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 text/0000-inclusive-ranges.md diff --git a/text/0000-inclusive-ranges.md b/text/0000-inclusive-ranges.md new file mode 100644 index 00000000000..800bdd8d191 --- /dev/null +++ b/text/0000-inclusive-ranges.md @@ -0,0 +1,79 @@ +- Feature Name: inclusive_range_syntax +- Start Date: 2015-07-07 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Allow a `x...y` expression to create an inclusive range. + +# Motivation + +There are several use-cases for inclusive ranges, that semantically +include both end-points. For example, iterating from `0_u8` up to and +including some number `n` can be done via `for _ in 0..n + 1` at the +moment, but this will fail if `n` is `255`. Furthermore, some iterable +things only have a successor operation that is sometimes sensible, +e.g., `'a'..'{'` is equivalent to the inclusive range `'a'...'z'`: +there's absolutely no reason that `{` is after `z` other than a quirk +of the representation. + +The `...` syntax mirrors the current `..` used for exclusive ranges: +more dots means more elements. + +# Detailed design + +`std::ops` defines + +```rust +pub struct RangeInclusive { + pub start: T, + pub end: T, +} +``` + +Writing `a...b` in an expression desugars to `std::ops::RangeInclusive +{ start: a, end: b }`. + +This struct implements the standard traits (`Clone`, `Debug` etc.), +but, unlike the other `Range*` types, does not implement `Iterator` +directly, since it cannot do so correctly without more internal +state. It can implement `IntoIterator` that converts it into an +iterator type that contains the necessary state. + +The use of `...` in a pattern remains as testing for inclusion +within that range, *not* a struct match. + +The author cannot forsee problems with breaking backward +compatibility. In particular, one tokenisation of syntax like `1...` +now would be `1. ..` i.e. a floating point number on the left, however, fortunately, +it is actually tokenised like `1 ...`, and is hence an error. + +# Drawbacks + +There's a mismatch between pattern-`...` and expression-`...`, in that +the former doesn't undergo the same desugaring as the +latter. (Although they represent essentially the same thing +semantically.) + +The `...` vs. `..` distinction is the exact inversion of Ruby's syntax. + +Only implementing `IntoIterator` means uses of it in iterator chains +look like `(a...b).into_iter().collect()` instead of +`(a..b).collect()` as with exclusive ones (although this doesn't +affect `for` loops: `for _ in a...b` works fine). + +# Alternatives + +An alternate syntax could be used, like +`..=`. [There has been discussion][discuss], but there wasn't a clear +winner. + +[discuss]: https://internals.rust-lang.org/t/vs-for-inclusive-ranges/1539 + +This RFC doesn't propose non-double-ended syntax, like `a...`, `...b` +or `...` since it isn't clear that this is so useful. Maybe it is. + +# Unresolved questions + +None so far. From 937d9adf039bf8aaa1239f248d7e3b8c5bc3702a Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 31 Jul 2015 16:46:37 -0700 Subject: [PATCH 2/3] Use the extra-field desugaring. --- text/0000-inclusive-ranges.md | 42 ++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/text/0000-inclusive-ranges.md b/text/0000-inclusive-ranges.md index 800bdd8d191..7783642eb18 100644 --- a/text/0000-inclusive-ranges.md +++ b/text/0000-inclusive-ranges.md @@ -29,25 +29,25 @@ more dots means more elements. pub struct RangeInclusive { pub start: T, pub end: T, + pub finished: bool, } ``` Writing `a...b` in an expression desugars to `std::ops::RangeInclusive -{ start: a, end: b }`. +{ start: a, end: b, finished: false }`. This struct implements the standard traits (`Clone`, `Debug` etc.), -but, unlike the other `Range*` types, does not implement `Iterator` -directly, since it cannot do so correctly without more internal -state. It can implement `IntoIterator` that converts it into an -iterator type that contains the necessary state. +and implements `Iterator`. The `finished` field is to allow the +`Iterator` implementation to work without hacks (see Alternatives). The use of `...` in a pattern remains as testing for inclusion within that range, *not* a struct match. The author cannot forsee problems with breaking backward compatibility. In particular, one tokenisation of syntax like `1...` -now would be `1. ..` i.e. a floating point number on the left, however, fortunately, -it is actually tokenised like `1 ...`, and is hence an error. +now would be `1. ..` i.e. a floating point number on the left, +however, fortunately, it is actually tokenised like `1 ...`, and is +hence an error with the current compiler. # Drawbacks @@ -58,10 +58,8 @@ semantically.) The `...` vs. `..` distinction is the exact inversion of Ruby's syntax. -Only implementing `IntoIterator` means uses of it in iterator chains -look like `(a...b).into_iter().collect()` instead of -`(a..b).collect()` as with exclusive ones (although this doesn't -affect `for` loops: `for _ in a...b` works fine). +Having an extra field in a language-level desugaring, catering to one +library use-case is a little non-"hygienic". # Alternatives @@ -74,6 +72,28 @@ winner. This RFC doesn't propose non-double-ended syntax, like `a...`, `...b` or `...` since it isn't clear that this is so useful. Maybe it is. +The `finished` field could be omitted, leaving two options: + +- `a...b` only implements `IntoIterator`, not `Iterator`, by + converting to a different type that does have the field. However, + this means that `a...b` behaves differently to `a..b`, so + `(a...b).map(|x| ...)` doesn't work (the `..` version of that is + used reasonably often, in the author's experience) +- `a...b` can implement `Iterator` for types that can be stepped + backwards: the only case that is problematic things cases like + `x...255u8` where the endpoint is the last value in the type's + range. A naive implementation that just steps `x` and compares + against the second value will never terminate: it will yield 254 + (final state: `255...255`), 255 (final state: `0...255`), 0 (final + state: `1...255`). I.e. it will wrap around because it has no way to + detect whether 255 has been yielded or not. However, implementations + of `Iterator` can detect cases like that, and, after yielding `255`, + backwards-step the second piece of state to `255...254`. + + This means that `a...b` can only implement `Iterator` for types that + can be stepped backwards, which isn't always guaranteed, e.g. types + might not have a unique predecessor (walking along a DAG). + # Unresolved questions None so far. From 17e23474ec708907c889e97d84aca1c6d478af62 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Fri, 31 Jul 2015 19:20:51 -0700 Subject: [PATCH 3/3] Propose `...b` too. --- text/0000-inclusive-ranges.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/text/0000-inclusive-ranges.md b/text/0000-inclusive-ranges.md index 7783642eb18..90a76cc8b94 100644 --- a/text/0000-inclusive-ranges.md +++ b/text/0000-inclusive-ranges.md @@ -31,13 +31,18 @@ pub struct RangeInclusive { pub end: T, pub finished: bool, } + +pub struct RangeToInclusive { + pub end: T, +} ``` Writing `a...b` in an expression desugars to `std::ops::RangeInclusive -{ start: a, end: b, finished: false }`. +{ start: a, end: b, finished: false }`. Writing `...b` in an +expression desugars to `std::ops::RangeToInclusive { end: b }`. -This struct implements the standard traits (`Clone`, `Debug` etc.), -and implements `Iterator`. The `finished` field is to allow the +`RangeInclusive` implements the standard traits (`Clone`, `Debug` +etc.), and implements `Iterator`. The `finished` field is to allow the `Iterator` implementation to work without hacks (see Alternatives). The use of `...` in a pattern remains as testing for inclusion @@ -59,7 +64,9 @@ semantically.) The `...` vs. `..` distinction is the exact inversion of Ruby's syntax. Having an extra field in a language-level desugaring, catering to one -library use-case is a little non-"hygienic". +library use-case is a little non-"hygienic". It is especially strange +that the field isn't consistent across the different `...` +desugarings. # Alternatives