如何在没有分配的情况下从 Vec 获取范围并在 Rust 中生成新的 Vec?

How to take range from Vec without allocations and produce new Vec in Rust?

我想从 Vec 中提取一个范围,方法是使用它并返回一个仅包含所需数据的新 Vec。我希望无需复制数据或进行任何分配即可实现这一点。在 C 中,我会根据需要移动指针、更改长度字段和释放内存。

目前我正在使用 drain,但我不确定它的性能特征(它会复制数据吗?)或它的惯用程度。例如,如果我想要前 4 个元素,我会这样做:

fn main() {
    let mut a = vec![1,2,3,4,5,6,7,8];

    // Only want to keep the first 4 elements
    let a: Vec<u32> = a.drain(..4).collect();
    
    println!("{:?}", a);
}

我看过 from_raw_parts 方法,但如果可能的话,我想避免使用指针之类的东西。

一般解决方案需要复制数据

一般来说,一个Vec

组成
  1. 一个整数capacity
  2. 可以包含 capacity 元素的内存区域(我将其称为 缓冲区 )和
  3. 一个整数 sizesize <= capacity

capacity 是缓冲区 在不调整大小的情况下可以 包含的元素数,size 是缓冲区 可以包含的元素数 当前包含。

保存在 Vec 中的元素位于缓冲区的开头(因此,从索引 0size - 1)。这意味着要创建一个包含旧 Vec 子范围的新 Vec,在一般情况下, 需要复制一些元素 – 如果子范围不从第一个元素开始,新 Vec 中的第一个元素将与旧 Vec 中的第一个元素不同,你不能不复制就重用缓冲区。

drain() 的情况下,这发生在 the Drop implementation of Drain:

                if self.0.tail_len > 0 {
                    unsafe {
                        let source_vec = self.0.vec.as_mut();
                        // memmove back untouched tail, update to new length
                        let start = source_vec.len();
                        let tail = self.0.tail_start;
                        if tail != start {
                            let src = source_vec.as_ptr().add(tail);
                            let dst = source_vec.as_mut_ptr().add(start);
                            ptr::copy(src, dst, self.0.tail_len);
                        }
                        source_vec.set_len(start + self.0.tail_len);
                    }
                }

如果要保留前面的元素

如果你只想保留前面的元素而删除后面的元素,有一个不复制元素的解决方案。您可以使用 truncate(). You can look at its source,它不会复制数据(在我看来,在这种情况下,它比使用 drain() 更符合习惯)。

fn main() {
    let mut a = vec![1, 2, 3, 4, 5, 6, 7, 8];

    a.truncate(4);
    
    assert_eq!(a, vec![1, 2, 3, 4]);
}

Playground link

为了完整起见:这不会缩小向量的容量。如果您截断了很多元素,然后保留截断后的 Vec 而不添加新元素,那么您就是在浪费内存。在那种情况下,截断后调用 shrink_to_fit() 可能是个好主意(尽管此 可能 复制 Vec 内容,因此更昂贵)。

drain() 也不应该在这种特殊情况下复制数据(self.0.tail_len == 0 在这种情况下,所以上面的代码片段不会被执行),但与直接使用 truncate