为什么在Rust中我能够从向量末尾开始切片?

14

给定 v = vec![1,2,3,4],为什么 v [4..] 返回一个空向量,但是 v [5..] 报错了,而 v [4]v [5] 都会报错?我怀疑这与在不指定起点或终点的情况下对切片进行实现有关,但我找不到任何在线信息。

2个回答

15
这是因为std::ops::RangeFrom的定义是“下界包含”,即使它没有上界限制。

简单回顾一下所有的管道: v[4..] 解糖成std::ops::Index,使用4..(解析为std::ops::RangeFrom)作为参数。std::ops::RangeFrom实现了std::slice::SliceIndex,而且Vec对于任何实现std::slice::SliceIndex的参数都有实现std::ops::Index。所以你看到的是使用RangeFromstd::ops::Index Vec

std::ops::RangeFrom被定义为始终包含下界,例如[0..]将包括要索引的第一个元素。如果(在你的情况下)Vec为空,则[0..]将是空片段。注意:如果下界不包含,则没有办法切片一个空的Vec而不会导致恼人的紧急情况。

一个简单的思考方法是“栅杆放在哪里”。

vec![0, 1, 2, 3]中的v [0..]

|  0    1    2    3   |
  ^
  |- You are slicing from here. This includes the
     entire `Vec` (even if it was empty)

v [4..]

|  0    1    2    3   |
                    ^
                    |- You are slicing from here to the end of the Vector.
                       Which results in, well, nothing.

如果使用 v[5..],则会是

|  0    1    2    3   |
                        ^
                        |- Slicing from here to infinity is definitely
                           outside the `Vec` and, also, the
                           caller's fault, so panic!

v[3..]

|  0    1    2    3   |
                ^
                |- slicing from here to the end results in `&[3]`

1
如果没有引起恐慌,那么根本无法切片一个空的Vec,这将是很麻烦的。按照这种推理,索引它也不应该引发恐慌吧? - Alexey Romanov
最终,这是设计师们有意为之的选择 - 尽管是一种选择。 - user2722968
我认为这对于搜索第一个已知出现并从那里切片的模式非常有用,例如 let idx = v.iter().position(|x| x == 5).unwrap_or(v.len()); let v = v[idx..];。即使在空向量上,此代码也永远不会出现错误。否则,我们需要执行 .unwrap_or(v.len() - 1),如果 v 为空,则可能会出现错误。 - Filipe Rodrigues
谢谢解释。我想普通向量索引可以被视为从那个“栅杆”开始返回第一个可用元素,但如果到达结尾就会出现恐慌。这就是行为不一致的原因,但仍然有点烦人。我的特定用例是我知道向量至少有两个元素,并且我得到了每个元素之外的所有元素的切片,如果向量恰好有两个元素,则只是一个空向量。 - Armadillan

4
虽然另一个答案解释了如何理解和记住Rust标准库中实现的索引行为,但它之所以是这样的真正原因与技术限制无关。这归结于Rust标准库作者所做的设计决策。
考虑到v = vec![1,2,3,4],为什么v[4..]返回一个空向量,但v[5..]会发生错误[..]?
因为是这样决定的。处理切片索引的下面代码(full source)将在起始索引大于切片长度时报错。
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);
}

它可以以不同的方式实现吗?我个人喜欢Python的实现方式。

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的方法未必比Rust更好,因为总是有一些权衡。Python的方法更方便(不需要检查起始索引是否大于长度),但如果出现错误,则很难找到问题,因为没有及早失败。
尽管Rust在技术上可以采用Python的方式,但其设计者决定通过恐慌提前失败,以便更快地发现错误,但代价是会带来一些不便(程序员需要确保起始索引不大于长度)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接