Given v = vec![1,2,3,4]
, why does v[4..]
return an empty vector, but v[5..]
panics, while both v[4]
and v[5]
panic? I suspect this has to do with the implementation of slicing without specifying either the start- or endpoint, but I couldn't find any information on this online.
Assign to variable x the last element of list items. let x = items. last(). unwrap();
As a result, you cannot use a Rust slice to insert, append or remove elements from the underlying container. Instead, you need either: to use a mutable reference to the container itself, to design a trait and use a mutable reference to said trait.
In Rust, there are several ways to initialize a vector. In order to initialize a vector via the new() method call, we use the double colon operator: let mut vec = Vec::new();
Vector is a module in Rust that provides the container space to store values. It is a contiguous resizable array type, with heap-allocated contents. It is denoted by Vec<T>. Vectors in Rust have O(1) indexing and push and pop operations in vector also take O(1) complexity.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With