Tracking issue for the start, end and new methods of RangeInclusive.
(Originally the tracking issue for rust-lang/rfcs#1980 the start and end fields, but we decided to make the representation private in https://github.com/rust-lang/rust/issues/49022#issuecomment-377533282)
Steps:
Unresolved questions:
This issue is separated from #28237 since there were vocal oppositions from the community (see #47813) due to performance loss if we commit to the two-field design.
If you want start and end, please use these for now:
x.start() ↦ x.next().unwrap()x.end() ↦ x.next_back().unwrap()Two important points here:
fold & friends), just like other types (such as Chain) that can be faster if you use internal iteration instead of a for loop.RangeInclusive compared to a Range, since the loop body fundamentally must do something different in the last iteration in the interesting caseAs I've said before, the optimal choice here would be to only have the range types be IntoIterator (not Iterator), but that change requires a time machine, so I think being like Range is best.
The libs team discussed this and the consensus was to make all fields private, provide accessor methods, and add a boolean field to implement iteration in a way that hopefully optimizes better.
impl<Idx> RangeInclusive<Idx> {
pub fn start(&self) -> &Idx {…}
pub fn end(&self) -> &Idx {…}
}
If someone has a use case for moving bounds out, they should request something like pub fn into_inner(self) -> (Idx, Idx) to be added. The methods of the RangeBounds trait (formerly RangeArgument) are to be renamed start_bound and end_bound to avoid name collision.
@rfcbot fcp merge
Team member @SimonSapin has proposed to merge this. The next step is review by the rest of the tagged teams:
No concerns currently listed.
Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
What exact this this FCPing? It feels odd that the last two times the class was modified it was via RFC, but this time it's just a "hopefully", not even a PR.
This is not making again deep changes to the data structure or syntax, only picking one of two already-proposed alternatives. And the more conservative one, since methods can keep working even if the internal structure changes.
Simply making the fields private will break lowering of ..= 😒
error[E0451]: field `start` of struct `std::ops::RangeInclusive` is private
--> librustc/ty/layout.rs:646:34
|
646 | self.valid_range == (0..=1)
| ^ field `start` is private
error[E0451]: field `end` of struct `std::ops::RangeInclusive` is private
--> librustc/ty/layout.rs:646:38
|
646 | self.valid_range == (0..=1)
| ^ field `end` is private
Therefore x..=y will need to lower to something like RangeInclusive::new(x, y). Should the constructor be exposed or #[doc(hidden)]? Or we keep the fields public and perma-unstable them?
I’d be fine with #[doc(hidden)] perma-unstable public fields, but we might want an constructor function anyway if the fields are "effectively private". Is lowering to a call difficult or inconvenient?
It needs a special case in https://github.com/rust-lang/rust/blob/56714acc5eb0687ed9a7566fdebe5528657fc5b/src/librustc/hir/lowering.rs#L3082 for the ExprKind::Range(Some(..), Some(..), Closed) case. Not very difficult, just different.
I'm not sure that a doc(hidden) perma-unstable function is better than doc(hidden) perma-unstable fields.
Why not stabilize that function? Other range types can be created as struct literals in addition to the dedicated syntax.
I agree with @SimonSapin, it seems reasonable to have a stable constructor for the type.
While i think those methods are fine, i believe (at least) the constructor should be const fn...
What advantage does the RangeInclusive::new have over just using ..=? And if there is something, wouldn't it also apply to having a Range::new in addition to ..? (I don't consider the compiler initializing unstable fields to be a problem.)
@scottmcm There is no advantage using the method.
..= and .. are simply syntactic sugar, a..b will be lowered as Range { start: a, end: b } before type-checking. The stability of the fields are irrelevant; the problem with ..= is that the compiler cannot initialize private fields (unless we use some hygiene hack to put the span inside std::ops which I think is even worse). Therefore a public method RangeInclusive::new() needs to be introduced as the lowering of a..=b.
The question is only whether we want to eventually stabilize RangeInclusive::new(), or keep it #[doc(hidden)] #[unstable] forever.
@kennytm Fair enough, makes sense if the goal is private fields.
A thought, since I don't know what was discussed: could the struct be #[non_exhaustive] so start and end are pub (and thus matchable, move-from-able, etc), but the struct can still be changed if needed? (That of course still needs the constructor, but might avoid future asks for into_inner, start_mut, etc.)
@scottmcm This will allow mutating start and end without touching the hidden field, potentially breaking some invariants for RangeInclusive.
:bell: This is now entering its final comment period, as per the review above. :bell:
The final comment period is now complete.
This makes it very difficult/impossible when start and end are non-Copy types.
(It has broken my math_traits crate, which relied on destructing RangeInclusive.)
If someone has a use case for moving bounds out, they should request something like pub fn into_inner(self) -> (Idx, Idx) to be added.
I hereby request pub fn into_inner(self) -> (Idx, Idx).
Adding into_inner sounds good to me. Wanna send a PR?
@SimonSapin done! https://github.com/rust-lang/rust/pull/50574