如何在Rust中将切片转换为数组?

136

我有一个大小未知的数组,我想要获取该数组的一个切片并将其转换为静态大小的数组:

fn pop(barry: &[u8]) -> [u8; 3] {
    barry[0..3] // expected array `[u8; 3]`, found slice `[u8]`
}

我该如何做到这一点?

8个回答

183
你可以轻松地使用Rust 1.34中稳定的TryInto trait来实现这一点:
// Before Rust 2021, you need to import the trait:
// use std::convert::TryInto;

fn pop(barry: &[u8]) -> [u8; 3] {
    barry.try_into().expect("slice with incorrect length")
}

但更好的是:无需克隆/复制您的元素!实际上可以从&[u8]获取&[u8; 3]

fn pop(barry: &[u8]) -> &[u8; 3] {
    barry.try_into().expect("slice with incorrect length")
}

如其他答案所述,如果barry的长度不是3,您可能不希望惊慌失措,而是要优雅地处理此错误。
这得益于相关特征TryFrom的实现(在Rust 1.47之前,这些仅适用于长度不超过32的数组)。
impl<'_, T, const N: usize> TryFrom<&'_ [T]> for [T; N]
where
    T: Copy, 

impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N]

impl<'a, T, const N: usize> TryFrom<&'a mut [T]> for &'a mut [T; N]

2
现在,impl<'a, T> TryFrom<&'a [T]> for [T; $N] where T: Copy 也已经提供了(与&'a [T; $N]相比)。 - updogliu
1
如果你的数组大于32字节,该如何处理呢? - Alvaro
1
另一种语法,从 Vec<[i32; 2]>::try_from(&vec[..]) - cambunctious
你是否意识到存在不安全的对应项?例如,使用 chunks_exact 时有一个保证。 - Jorge Leitao
1
最佳答案!FYI use std::convert::TryInto; 在2021版中似乎不再需要了:https://doc.rust-lang.org/std/prelude/index.html - jaques-sam
显示剩余3条评论

22
感谢@malbarbo,我们可以使用这个帮助函数:
use std::convert::AsMut;

fn clone_into_array<A, T>(slice: &[T]) -> A
where
    A: Default + AsMut<[T]>,
    T: Clone,
{
    let mut a = A::default();
    <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
    a
}

获得更整洁的语法:

fn main() {
    let original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let e = Example {
        a: clone_into_array(&original[0..4]),
        b: clone_into_array(&original[4..10]),
    };

    println!("{:?}", e);
}

只要实现了 T: Default + Clone,就可以使用这种形式。
如果您知道自己的类型实现了 Copy,则可以使用以下形式:
use std::convert::AsMut;

fn copy_into_array<A, T>(slice: &[T]) -> A
where
    A: Default + AsMut<[T]>,
    T: Copy,
{
    let mut a = A::default();
    <A as AsMut<[T]>>::as_mut(&mut a).copy_from_slice(slice);
    a
}

如果目标数组和传入的切片长度不相同,两种变体都会 panic!


2
在这行代码 let mut a = Default::default(); 中,可能应该改为 let mut a = A::default();,对吗? - Constantine
1
@KostyaKrivomaz:两者都可以。<A as Default>::default()是另一种变体。 - Matthieu M.
Clippy 更倾向于使用 A::default(),因为它更加明确。 - Jocelyn
1
@Jocelyn:最好还是遵守规矩吧! - Matthieu M.

19

我建议使用 arrayref 这个 crate,它有一个很方便的宏可以做到这一点。

请注意,使用此 crate,您将创建一个对数组的引用,&[u8; 3],因为它不会克隆任何数据!

如果您确实想要克隆数据,则仍然可以使用该宏,但在最后调用clone函数:

#[macro_use]
extern crate arrayref;

fn pop(barry: &[u8]) -> &[u8; 3] {
    array_ref!(barry, 0, 3)
}
或者
#[macro_use]
extern crate arrayref;

fn pop(barry: &[u8]) -> [u8; 3] {
    array_ref!(barry, 0, 3).clone()
}

15

这里是一个符合你要求的类型签名的函数。

fn pop(barry: &[u8]) -> [u8; 3] {
    [barry[0], barry[1], barry[2]]
}

但是,由于barry可能少于三个元素,您可能希望返回一个Option<[u8; 3]>而不是一个[u8; 3]

fn pop(barry: &[u8]) -> Option<[u8; 3]> {
    if barry.len() < 3 {
        None
    } else {
        Some([barry[0], barry[1], barry[2]])
    }
}

23
  1. 因为通用情况没有被解决(例如 [u8; 32];没有人会重复使用 32 次索引访问)
- diralik
2
@diralik 然而还是有人这么做... 在常量泛型稳定之前,这仍然是最好的方法。当前的 Rust std 实际上有一个不稳定的 trait,它确保数组长度最多为 32,并实现了大多数对于每个单独的数组类型都有意义的 trait,最多实现了 32 次...这就是现实。你可以编写代码生成器,随意输出从 0 到 N 的数组代码,以使其变得更好,这个答案很好,展示了 std 是如何做到的。或者你只是要说“很遗憾,五年后再见”对于大小为 33 的数组? - Yamirui
3
更新:常量泛型已稳定化 ;) - Bobbbay

15

你可以手动创建数组并返回它。

以下是一个函数,如果你想要获取超过3个元素或者少于3个元素也很容易扩展。

请注意,如果切片太小,则数组的末尾项将为0。

fn pop(barry: &[u8]) -> [u8; 3] {
    let mut array = [0u8; 3];
    for (&x, p) in barry.iter().zip(array.iter_mut()) {
        *p = x;
    }
    array
}

1
@Levins 这太棒了! - mwhittaker
1
最好使用通用函数 - 如果您将来需要将其用于非 u8 类型,则需要为每种类型定义函数会变得很烦人。 - ideasman42

3

我不满意其他答案,因为我需要返回长度不同的定长u8数组的几个函数。 我编写了一个宏,用于生成特定任务的函数。 希望对其他人有所帮助。

#[macro_export]
macro_rules! vec_arr_func {
    ($name:ident, $type:ty, $size:expr) => {
        pub fn $name(data: std::vec::Vec<$type>) -> [$type; $size] {
            let mut arr = [0; $size];
            arr.copy_from_slice(&data[0..$size]);
            arr
        }
    };
}

//usage - pass in a name for the fn, type of array, length
vec_arr_func!(v32, u8, 32);
v32(data); //where data is std::vec::Vec<u8>

4
其它回答有什么问题让你不满意?这个回答有什么更好的地方? - Shepmaster
“函数打印宏”是什么意思? - Shepmaster
顺便说一下,在函数末尾使用return不是惯用语。 - Shepmaster
这段代码与此答案实际上是相同的,只不过它使用了一个宏。 - Shepmaster
1
感谢所有的评论。我是 Rust 的新手。其他答案中不清楚的是我们是否可以改变返回的固定数组的长度。我无法通过函数来实现这一点,所以只能使用宏。你说得对,返回值是一种习惯。 - mattdlockyer

2

Vec、'Slice'和Array之间的共同点是Iter,因此您可以像这样简单地同时使用zipmap

let x = vec![1, 2, 3];
let mut y: [u8; 3] = [Default::default(); 3];
println!("y at startup: {:?}", y);    
x.iter().zip(y.iter_mut()).map(|(&x, y)| *y = x).count();
println!("y copied from vec: {:?}", y);

在此输入图片描述

这是因为数组是一维数组。

为了同时测试向量、切片和数组,请参考此处

let a = [1, 2, 3, 4, 5];
let slice = &a[1..4];
let mut x: Vec<u8> = vec![Default::default(); 3];
println!("X at startup: {:?}", x);    
slice.iter().zip(x.iter_mut()).map(|(&s, x)| *x = s).count();
println!("X copied from vec: {:?}", x);

另一个比逐字复制更快的选项是:

在此输入图片描述

y[..x.len()].copy_from_slice(&x);

适用于所有人,以下是示例:

let a = [1, 2, 3, 4, 5];
let mut b: Vec<u8> = vec![Default::default(); 5];
b[..a.len()].copy_from_slice(&a);
println!("Copy array a into vector b: {:?}", b);

let x: Vec<u8> = vec![1, 2, 3, 4, 5];
let mut y: [u8; 5] = [Default::default(); 5];
y[..x.len()].copy_from_slice(&x);
println!("Copy vector x into array y: {:?}", y);

enter image description here


0
值得注意的是,如果你的数组是一个字符串,例如,如果你想要将`&'static str -> [u32; N]`进行转换,那么有时候你可以直接将原始字符串初始化为`b"my string"`。在Rust中,`b""`语法创建了静态已知大小的字节数组(例如,`&[u8, 32]`)。

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