如何将Vec转换为数组而不复制元素?

6

我有一个非平凡类型的Vec,我确定它的大小。我需要将其转换为固定大小的数组。理想情况下,我希望能够:

  1. 不复制数据-我想使用Vec
  2. 不预先初始化数组为零数据,因为这将浪费CPU周期。

问题以代码形式编写:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = vec![
        Point { x: 1, y: 2 },
        Point { x: 3, y: 4 },
        Point { x: 5, y: 6 },
    ];

    // I would like this to be an array of points
    let array: [Point; 3] = ???;
}

这似乎是一个微不足道的问题,但我无法在 Vec 文档、Rust 书籍的切片部分或通过谷歌搜索找到令人满意的解决方案。唯一的方法是首先用零数据初始化数组,然后从 Vec 复制所有元素,但这不符合我的要求。


1
这可能是 https://dev59.com/Il0Z5IYBdhLWcg3whApv 和 https://dev59.com/XKLia4cB1Zd3GeqPcwzc 的重复问题。 - ArtemGr
不是 https://dev59.com/Il0Z5IYBdhLWcg3whApv 的重复,但它可以作为解决方案的一部分,尽管使用了 unsafe 关键字。话虽如此,如果没有更简单、更优雅的解决方案,我会感到惊讶。 - Fireant
没有动态长度(alloca)数组本身的用处较小,通常会使用某种更高级别的结构(https://github.com/servo/rust-smallvec?)代替。当动态长度数组出现时,我期望会出现更符合人体工程学的解决方案。 - ArtemGr
2
为了让问题更具体化:它应该支持特定长度的数组,还是任意长度的数组?显然,编写一个安全且简单的接口来执行此操作并不容易。对于像 [T; 3] 这样的单个数组长度,会有简单的解决方案。 - bluss
是的,固定长度数组是有意和需要的,而动态长度则不是。在API中,固定长度数组指定了传递的数据形状的契约。例如,函数create_triangle将使用由3个点组成的固定长度数组,因为三角形由3个点定义。它不能使用任何其他长度的点数组。因此,我认为可以说数组长度增加了关于使用的额外信息(这也意味着长度不需要在运行时进行验证)。 - Fireant
3个回答

6

正确地执行此操作非常困难。问题在于在部分未初始化的数组时正确处理恐慌。如果数组中的类型实现了Drop,那么它将访问未初始化的内存,导致未定义的行为。

最简单、最安全的方法是使用arrayvec

extern crate arrayvec;

use arrayvec::ArrayVec;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = vec![
        Point { x: 1, y: 2 },
        Point { x: 3, y: 4 },
        Point { x: 5, y: 6 },
    ];

    let array: ArrayVec<_> = points.into_iter().collect();
    let array: [Point; 3] = array.into_inner().unwrap();
    println!("{:?}", array);
}

请注意,这仅适用于特定大小的数组,因为Rust尚未具备通用整数功能。into_inner也有一个性能警告需要注意。

另请参见:


3

还有 try_into 吗?

use std::convert::TryInto;

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let v: Vec<Point> = vec![Point { x: 1, y: 1 }, Point { x: 2, y: 2 }];
    let a: &[Point; 2] = v[..].try_into().unwrap();
    println!("{:?}", a);
}

它以不可变方式借用,因此Vec不会被消耗。


2

仅为娱乐,以下示例展示了安全的 Rust 为我们提供了在小规模特定大小上进行操作的方式,例如:

/// Return the array inside Some(_), or None if there were too few elements
pub fn take_array3<T>(v: &mut Vec<T>) -> Option<[T; 3]> {
    let mut iter = v.drain(..);
    if let (Some(x), Some(y), Some(z))
        = (iter.next(), iter.next(), iter.next())
    {
        return Some([x, y, z]);
    }
    None
}

/// Convert a Vec of length 3 to an array.
///
/// Panics if the Vec is not of the exact required length
pub fn into_array3<T>(mut v: Vec<T>) -> [T; 3] {
    assert_eq!(v.len(), 3);
    let z = v.remove(2);
    let y = v.remove(1);
    let x = v.remove(0);
    [x, y, z]
}

Vec 的基本方式之一是使用 removepopdraininto_iter 等方法来获取其元素的所有权。


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