Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upSigned integer indexing and slicing #2249
Comments
clarfon
changed the title
Signed integer indexing
Signed integer indexing and slicing
Dec 14, 2017
This comment has been minimized.
This comment has been minimized.
|
Also should clarify this applies to indexing as well as slicing, i.e. |
Centril
added
the
T-libs
label
Dec 14, 2017
This comment has been minimized.
This comment has been minimized.
|
Sounds like this could be really neat when dealing with things like "index to the player on the left" where you subtract when going to the left or the dining philosophers problem-like cases. Please write that RFC =) |
This comment has been minimized.
This comment has been minimized.
|
@Centril honestly the main reason why I asked is I'm legitimately confused why this hasn't been brought up yet. I feel like there's a reason why this wasn't done that I wasn't able to find, so, I figured I'd ask what people think before writing something up. |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Dec 15, 2017
•
|
One thing that's bothersome about Python's solution is that def last_n_items(xs, n):
return xs[-n:]
last_n_items(range(10), 3) # [7, 8, 9]
last_n_items(range(10), 2) # [8, 9]
last_n_items(range(10), 1) # [9]
last_n_items(range(10), 0) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]For this reason I could never see this as being incorporated into rust as indexing by actual negative integers. Maybe indexing by some new enum type with a |
This comment has been minimized.
This comment has been minimized.
|
Negative indices in C mean something completely different and are super scary (but are defined behaviour!!). Imitating the python behaviour will be surprising to anyone with a C/C++ background and would be a bad idea IMO. Alternative suggestions I would prefer really much:
|
This comment has been minimized.
This comment has been minimized.
|
I think we should here first and foremost focus on semantics and not whether or not we should have a wrapper type around That said, |
This comment has been minimized.
This comment has been minimized.
A question that every proposal to change the language has to answer: why should it be in the standard library instead of being inside a third party crate? I believe it deserves to be in the standard library, especially given that python has support for this too. I am only against the actual |
This comment has been minimized.
This comment has been minimized.
|
@est31 In an effort to cheat Wadler's law I will agree with you on the concrete syntax To be clear, what I was proposing was (playground): // Inside core::ops:
pub trait Index<Idx>
where
Idx: ?Sized,
{
type Output: ?Sized;
fn index(&self, index: Idx) -> &Self::Output;
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] // <- discuss
pub struct FromEnd<T> {
pub index: T,
}
pub fn end<T>(index: T) -> FromEnd<T> { // <- bikeshed name
FromEnd { index }
}
impl<T> From<T> for FromEnd<T> {
fn from(index: T) -> Self { end(index) }
}
impl<T> Index<FromEnd<usize>> for Vec<T> {
type Output = T;
fn index(&self, index: FromEnd<usize>) -> &Self::Output {
// Panicing is taken care of for us... so no buffer underflow.
&self[self.len() - 1 - index.index]
}
}
fn main() {
let vec = (0..10).collect::<Vec<_>>();
println!("{:?}", vec.index(end(0)));
// ^- obviously vec[end(0)] but this isn't inside libcore.
} |
This comment has been minimized.
This comment has been minimized.
|
Hmm.. on second thought, I think |
This comment has been minimized.
This comment has been minimized.
To add to your argument for the proposition, if you replace the trait definition above with:
you run into coherence troubles:
Therefore it has to be in libcore, or no dice. |
This comment has been minimized.
This comment has been minimized.
|
I think that having a I may go ahead with the |
This comment has been minimized.
This comment has been minimized.
|
@clarcharr If you want to co-author I'm interested =) You can find me at #rust-lang @ irc.mozilla.org for quicker discussions. |
This comment has been minimized.
This comment has been minimized.
|
@Centril I will get in touch! :) I need to rethink this for a bit though. Right now, thinking again, it still makes sense to support signed indices for the sake of ranges. For example, I also question the usefulness of negative indices from a C background. Doing such an index is inherently unsafe, and Rust allowing it leads to suspicion. I feel like that problem could easily be avoided by having a clippy lint that points out negative indexing, potentially enabled alongside a bunch of other "catch-ups for C Devs" lints. |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov and @koutheir, mind adding any extra thoughts considering how you both downvoted this? |
This comment has been minimized.
This comment has been minimized.
@Centril good point!
I have personally could have used
As I've said in my comment, negative indexing is defined behaviour in C. I've linked to a discussion on stack overflow where the C standard was quoted. char *c = malloc(40);
c += 20;
c[-1] = 10;
printf("%d\n", c[-1]);All non-ub "safe" C here, except that you should maybe verify that malloc doesnt return NULL. |
This comment has been minimized.
This comment has been minimized.
Right now, pub struct Range<Idx> {
pub start: Idx,
pub end: Idx,
}It could be extended to be a struct like pub struct Range<IdxStart, IdxEnd=IdxStart> {
pub start: IdxStart,
pub end: IdxEnd,
}This would be 100% backwards compatible, wouldnt it? |
This comment has been minimized.
This comment has been minimized.
|
Also, if you went with the signed indices proposal, you'd have to change how the compiler is doing inference when it sees an indexing operation in order to make stuff work. Right now, impl'ing index for isize will totally break inference: Although inference breakages are not guaranteed by the language, I think in this case the breakage would be too much to not be fixed by a compiler change :). I'm not even 100% sure whether inference happens before or after operators are desugared, if it happens after, the required changes would be even more wide-ranging. |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Dec 16, 2017
•
|
My thoughts on
Edit: Actually, I can also think of an alternative model, but I'm not sure if I like it:
Edit 2: Argh! I just noticed that neither of these models let you do something like |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Dec 16, 2017
•
|
I guess there are really multiple distinct things that python's negative indices are useful for, none of which it is really ideal for, and which may each be better served by distinct apis in rust. My current thoughts are at least two features:
impl<T> [T] {
pub fn rsplit_at(&self, k: usize) -> (&[T], &[T]) {
self.split_at(self.len() - k)
}
}(note: A disadvantage of |
This comment has been minimized.
This comment has been minimized.
le-jzr
commented
Dec 17, 2017
•
|
WRT original proposal: IMO this would reduce safety by making accidental index underflows harder to detect. Typing |
This comment has been minimized.
This comment has been minimized.
|
I'm glad to see this leading away from indexing by mixed ranges. The thing I'm saddest about there are situations like "is A bunch of the scenarios I've seen in the thread remind me of iterator adapters, just directly on slices. For example, |
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Dec 18, 2017
•
|
The very nice solution of D language:
That is equivalent to Python:
In D you can even overload the $ (defining opDollar) if you want to define multi-dimensional tensor-like data structures (you overload each $ for each dimension of the tensor): |
This comment has been minimized.
This comment has been minimized.
|
@scottmcm that's actually a nice idea! All of these adapters should be constant-time for slice iterators, and perhaps there may be a way to add convenience functions if they're desired. |
This comment has been minimized.
This comment has been minimized.
Gilgamesh22
commented
May 23, 2018
•
|
Random person from the internet here Basic
Ranges
Reverse ranges
Negative zero for inclusive reverse ranges
I understand that some of these may seem weird but I just wanted to show what could be the equivalent using iterators. |
This comment has been minimized.
This comment has been minimized.
|
I personally really like the idea from D of having a value like For example, instead of |
This comment has been minimized.
This comment has been minimized.
roblabla
commented
Oct 8, 2018
|
C# 8 implemented it with |
This comment has been minimized.
This comment has been minimized.
|
I made a crate to try out the "slice adapter" idea above (#2249 (comment)) let r = [1, 2, 4, 9, 16, 25].rev();
assert_eq!(r[0], 25);
assert_eq!(r[1..3].rev(), &[9, 16]);
assert_eq!(r.split_first().unwrap().0, &25);
let mut it = r.iter().cloned().skip(2);
assert_eq!(it.next(), Some(9));
assert_eq!(it.next(), Some(4));
assert_eq!(it.next(), Some(2)); |
This comment has been minimized.
This comment has been minimized.
mathieucaroff
commented
Jan 2, 2019
|
Um, I'm from Python where we have iterators (about any container) and sequences (indexable, lengthed containers). I see iterators have a .count(), method, which is a bit confusing, but I'm too lazy to RTFM. So would someone kind link the documentation or explain what is what. Otherwise, I like the solution used in D. It also exists in GNU Octave, where they reused the keyword |
This comment has been minimized.
This comment has been minimized.
|
In general, in Rust you can’t efficiently know the length of an iterator. You just have consume all the elements and count them. This is what the count() method does. |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Jan 3, 2019
|
Right, it's very different from python's |
This comment has been minimized.
This comment has been minimized.
|
The C# example -- which works by having a newtype that means "from the end" reminded me that we have (I can't decide if this is a good idea or an abuse of the type, though.) |
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Feb 24, 2019
|
To me using something like the D syntax (with # instead of $) seems way better than |
clarfon commentedDec 14, 2017
I haven't found an RFC or Rust issue for this, so, I figured I'd mention it here. The only ones I've seen suggested panicking on negative values, which would not be desirable.
Is there a reason why there aren't impls for signed indexing? For example, being able to make slices like
s[..-2]might be useful. This would still technically be opt-in, as indexing onusizewould not have the extra branch, but indexing onisizewould.And just to clarify, negative indexes like
-1and-2get added to the length of the slice to become real indices, likelen - 1andlen - 2. If the value is less than the absolute value of the length, then it would be marked as OOB like any other index.This seems pretty ergonomic as languages like Python and Bash have been using negative indices for a long time. Technically, this would be a bigger project than simply adding
Indexand other impls, as the const evaluator would also need to be updated.If there is desire for this, I'd be happy to write an RFC for it.