For the index from end case you had the solution in the first version of the question, calculate the start index and use it instead. Of course you can do that programatically as well:
let h = "hellow world";
assert_eq!(h[h.len()-5..].to_owned(), "world".to_owned());
For the reverse case you got 2 options:
- use
rev()on a corresponding iterator:
for c in "hello world".chars().rev() {
print!("{c}");
}
println!();
prints dlrow olleh
- reverse the slice in place (which is complicated for utf-8 so I show it with bytes instead):
let s: &mut [u8] = &mut [1, 2, 3];
s.reverse();
assert_eq!(s, &[3, 2, 1]);
Playground link for all the examples in one go
Answer from cafce25 on Stack OverflowWhy can I start a slice past the end of a vector in Rust? - Stack Overflow
Why is slice end_index logic as it is?
Neater way to access the last n elements in a vec?
You can use an endless range:
let vec = vec![1, 2, 3, 4, 5];
println!("Remaining: {:?}", &vec[2..]);Prints: "Remaining: [3, 4, 5]"
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b1a03af4ae475b294030f3d5d43b5ad
More on reddit.comCleanest way to get the index of an element in a slice by pointer/reference?
This is simply because std::ops::RangeFrom is defined to be "bounded inclusively below".
A quick recap of all the plumbing: v[4..] desugars to std::ops::Index using 4.. (which parses as a std::ops::RangeFrom) as the parameter. std::ops::RangeFrom implements std::slice::SliceIndex and Vec has an implementation for std::ops::Index for any parameter that implements std::slice::SliceIndex. So what you are looking at is a RangeFrom being used to std::ops::Index the Vec.
std::ops::RangeFrom is defined to always be inclusive on the lower bound. For example [0..] will include the first element of the thing being indexed. If (in your case) the Vec is empty, then [0..] will be the empty slice. Notice: if the lower bound wasn't inclusive, there would be no way to slice an empty Vec at all without causing a panic, which would be cumbersome.
A simple way to think about it is "where the fence-post is put".
A v[0..] in a vec![0, 1, 2 ,3] is
| 0 1 2 3 |
^
|- You are slicing from here. This includes the
entire `Vec` (even if it was empty)
In v[4..] it is
| 0 1 2 3 |
^
|- You are slicing from here to the end of the Vector.
Which results in, well, nothing.
while a v[5..] would be
| 0 1 2 3 |
^
|- Slicing from here to infinity is definitely
outside the `Vec` and, also, the
caller's fault, so panic!
and a v[3..] is
| 0 1 2 3 |
^
|- slicing from here to the end results in `&[3]`
While the other answer explains how to understand and remember the indexing behavior implemented in Rust standard library, the real reason why it is the way it is has nothing to do with technical limitations. It comes down to the design decision made by the authors of Rust standard library.
Given
v = vec![1,2,3,4], why doesv[4..]return an empty vector, butv[5..]panics [..] ?
Because it was decided so. The code below that handles slice indexing (full source) will panic if the start index is larger than the slice's length.
fn index(self, slice: &[T]) -> &[T] {
if self.start > slice.len() {
slice_start_index_len_fail(self.start, slice.len());
}
// SAFETY: `self` is checked to be valid and in bounds above.
unsafe { &*self.get_unchecked(slice) }
}
fn slice_start_index_len_fail(index: usize, len: usize) -> ! {
panic!("range start index {} out of range for slice of length {}", index, len);
}
How could it be implemented differently? I personally like how Python does it.
v = [1, 2, 3, 4]
a = v[4] # -> Raises an exception - Similar to Rust's behavior (panic)
b = v[5] # -> Same, raises an exception - Also similar to Rust's
# (Equivalent to Rust's v[4..])
w = v[4:] # -> Returns an empty list - Similar to Rust's
x = v[5:] # -> Also returns an empty list - Different from Rust's, which panics
Python's approach is not necessarily better than Rust's, because there's always a trade-off. Python's approach is more convenient (there's no need to check if a start index is not greater than the length), but if there's a bug, it's harder to find because it doesn't fail early.
Although Rust can technically follow Python's approach, its designers decided to fail early by panicking in order that a bug can be faster to find, but with a cost of some inconvenience (programmers need to ensure that a start index is not greater than the length).
I'm used to being able to do this in Python with my_list[-n:] but I can't find an equivalent for Rust.
I've been doing:
let index = my_vec.len() - (n+1); let last_n = my_vec.get(index..);
but this seems messy. Is there a better way?
EDIT: My latest solution is my_vec.windows(n).last() which creates an iterator of overlapping subslices of length n and then takes the last element. Benchmarked it and it's 8% slower. Not sure if I'm going to notice that difference or not. I also tried my_vec.rchunks(n).nth(0) but that was 40% slower than my original method.
You can use an endless range:
let vec = vec![1, 2, 3, 4, 5];
println!("Remaining: {:?}", &vec[2..]);
Prints: "Remaining: [3, 4, 5]"
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b1a03af4ae475b294030f3d5d43b5ad
You could wrap that in a method, by making your own trait and implementing it for Vec.