使用宏初始化一大型非可复制元素数组

5

我正在尝试使用相同的初始化器初始化一个大数组。64个元素只是一个例子 - 我想至少有16k个元素。不幸的是,一个简单的

let array : [AllocatedMemory<u8>; 64] = [AllocatedMemory::<u8>{mem:&mut []};64];

无法工作,因为AllocatedMemory结构体没有实现Copy

error: the trait `core::marker::Copy` is not implemented for the type `AllocatedMemory<'_, u8>` [E0277]
let array : [AllocatedMemory<u8>; 64] = [AllocatedMemory::<u8>{mem:&mut []}; 64];
                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

所以我尝试使用宏,但没有成功:

struct AllocatedMemory<'a, T: 'a> {
    mem: &'a mut [T],
}

macro_rules! init_memory_helper {
    (1, $T : ty) => { AllocatedMemory::<$T>{mem: &mut []} };
    (2, $T : ty) => { init_memory_helper!(1, $T), init_memory_helper!(1, $T) };
    (4, $T : ty) => { init_memory_helper!(2, $T), init_memory_helper!(2, $T) };
    (8, $T : ty) => { init_memory_helper!(4, $T), init_memory_helper!(4, $T) };
    (16, $T : ty) => { init_memory_helper!(8, $T), init_memory_helper!(8, $T) };
    (32, $T : ty) => { init_memory_helper!(16, $T), init_memory_helper!(16, $T) };
    (64, $T : ty) => { init_memory_helper!(32, $T), init_memory_helper!(32, $T) };
}

macro_rules! init_memory {
    (1, $T : ty) => { [init_memory_helper!(1, $T)] };
    (2, $T : ty) => { [init_memory_helper!(2, $T)] };
    (4, $T : ty) => { [init_memory_helper!(4, $T)] };
    (8, $T : ty) => { [init_memory_helper!(8, $T)] };
    (16, $T : ty) => { [init_memory_helper!(16, $T)] };
    (32, $T : ty) => { [init_memory_helper!(32, $T)] };
    (64, $T : ty) => { [init_memory_helper!(64, $T)] };
}

fn main() {
    let array: [AllocatedMemory<u8>; 64] = init_memory!(64, u8);
    println!("{:?}", array[0].mem.len());
}

错误信息为:
error: macro expansion ignores token `,` and any following
    (64, $T : ty) => { init_memory_helper!(32, $T), init_memory_helper!(32, $T) };
note: caused by the macro expansion here; the usage of `init_memory_helper!` is likely invalid in expression context

有没有一种方法可以在不剪切和粘贴每个初始化程序的情况下初始化此数组?

相关:有没有一种方法可以使用宏计数,tl;dr -> 不能用普通的宏。 - Lukas Kalbertodt
3个回答

6
问题在于宏的扩展必须是一个完整且独立有效的语法元素。你不能像扩展到a, b一样扩展到42 +。在Rust中也没有(静态地)连接或合并数组的方法;整个数组初始化器必须在一个步骤中扩展。
这可以使用下推累积的宏来完成。诀窍在于将尚未具有语法有效性的部分数组表达式向下递归,而不是在回溯时构造。当您到达扩展的底部时,一次性发出完整的表达式。
以下是支持长度为0到8的数组以及2的幂次方长达64的宏:
macro_rules! array {
    (@accum (0, $($_es:expr),*) -> ($($body:tt)*))
        => {array!(@as_expr [$($body)*])};
    (@accum (1, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)*))};
    (@accum (2, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (0, $($es),*) -> ($($body)* $($es,)* $($es,)*))};
    (@accum (3, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (2, $($es),*) -> ($($body)* $($es,)*))};
    (@accum (4, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (2, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (5, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)*))};
    (@accum (6, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)* $($es,)*))};
    (@accum (7, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (4, $($es),*) -> ($($body)* $($es,)* $($es,)* $($es,)*))};
    (@accum (8, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (4, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (16, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (8, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (32, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (16, $($es,)* $($es),*) -> ($($body)*))};
    (@accum (64, $($es:expr),*) -> ($($body:tt)*))
        => {array!(@accum (32, $($es,)* $($es),*) -> ($($body)*))};

    (@as_expr $e:expr) => {$e};

    [$e:expr; $n:tt] => { array!(@accum ($n, $e) -> ()) };
}

fn main() {
    let ones: [i32; 64] = array![1; 64];
    println!("{:?}", &ones[..]);
}

这里的策略是将输入大小乘以二的幂,并为非二次幂添加余数。这是为了避免宏递归限制(我认为默认值为64),通过确保$n$快速降低价值。
仅为了预防经常出现的后续问题:,你不能用算术简化这个问题;你不能在宏中进行算术运算。:) 附加说明: 如果您不确定如何使用此功能,请在编译时传递-Z trace-macrosrustc,并查看每个被展开的宏调用。以使用array![1; 6]为例,您会得到类似于以下内容:
array! { 1 ; 6 }
array! { @ accum ( 6 , 1 ) -> (  ) }
array! { @ accum ( 4 , 1 ) -> ( 1 , 1 , ) }
array! { @ accum ( 2 , 1 , 1 ) -> ( 1 , 1 , ) }
array! { @ accum ( 0 , 1 , 1 ) -> ( 1 , 1 , 1 , 1 , 1 , 1 , ) }
array! { @ as_expr [ 1 , 1 , 1 , 1 , 1 , 1 , ] }

3
这些宏的问题在于前者不能在Rust中产生有效的语法形式——由逗号组合的两个表达式本身不是有效的形式。它被“注入”到另一个宏的方括号中是无关紧要的。
坦白地说,我不知道如何使用常规数组正确地做到这一点。缺乏数字作为泛型参数是一个众所周知的问题,这阻碍了许多有用的模式。例如,如果支持它们,就可以拥有像这样的函数:
fn make_array<T, N: usize, F>(f: F) -> [T; N] where F: FnMut() -> T

创建一个任意大小的数组,并用函数调用的结果填充它:
let array: [_; 64] = make_array(|| AllocatedMemory::<u8>{ mem: &mut [] })

但是,目前 Rust 还没有提供这样的结构。你必须使用动态结构,比如 Vec。你也可以尝试使用 arrayvec,它为某些固定大小的数组提供了类似 Vec 的 API;使用它,你可以做到这样:

use arrayvec::ArrayVec; // 0.5.1

fn main() {
    let mut array = ArrayVec::<[_; 64]>::new();
    for _ in 0..array.len() {
        array.push(AllocatedMemory::<u8> { mem: &mut [] });
    }
    let array = array.into_inner(); // array: [AllocatedMemory<u8>; 64]
}

另请参阅:


这是一个非常酷的库——不幸的是,我忘了提到我想避免不安全的领域,特别是第三方代码。 - hellcatv
我实际上只需要一个列表接口...我想知道是否可以使用一些宏来生成大小为N的列表,也许是某种带有选项的侵入式东西。 - hellcatv
@hellcatv 如果你即使使用了最少的标准库,也会依赖于不安全的代码。几乎所有有用的抽象(集合,内存管理,I/O,线程等)都是基于不安全的代码,没有它们无法真正实现。在不安全的内部接口周围使用安全接口并没有错。你不必亲自使用unsafe - Vladimir Matveev

0

一个"安全的"实现,运行在稳定的平台上,受到Reddit的启发:

// #![feature(core_intrinsics)]
// use std::ptr;
use std::mem;
use std::mem::MaybeUninit;

type MyStructValue = Vec<usize>;
type UsizeToVecBuilder = Box<dyn Fn(usize) -> Vec<usize>>;

#[derive(Debug)]
struct MyStruct {
    value: MyStructValue,
}

macro_rules! make_array {
    ([$t:ident; $n:expr], $constructor:expr, $builder:expr) => {{
        let mut data: [MaybeUninit<$t>; $n] = unsafe { MaybeUninit::uninit().assume_init() };

        let mut i: usize = 0;
        for elem in &mut data[..] {
            *elem = MaybeUninit::new($constructor(i, $builder));
            i += 1;
        }

        unsafe { mem::transmute::<_, [$t; $n]>(data) }
    }};
}

fn main() {
    println!(
        "{:?}",
        make_array!(
            [MyStruct; 5],
            |i, b: UsizeToVecBuilder| MyStruct { value: b(i) },
            Box::new(|i| (0..i + 1).collect())
        )
    );
}

// unstable version: (see reddit: https://www.reddit.com/r/rust/comments/29ymbx/a_macro_to_fill_a_fixed_length_array/)
//
// macro_rules! make_array {
//     ($n:expr, $constructor:expr) => {{
//         let mut items: [_; $n] = unsafe { mem::uninitialized() };
//         for i in 0..$n {
//             let val = $constructor(i);
//             unsafe {
//                 std::intrinsics::volatile_copy_nonoverlapping_memory(
//                     &mut items[i], &val, 1
//                 );
//                 // ptr::copy_nonoverlapping_memory(&mut items[i], &val, 1);
//                 mem::forget(val);
//             }
//         }
//         items
//     }}
// }

// fn main() {
// unstable version:
// println!("{:?}", make_array!(5, |i| MyStruct { value: i }));
// }

我建议大多数人不要使用这个答案。ArrayVec,如Vladimir Matveev的答案所示,在底层执行不安全代码并提供更好的API。 - Shepmaster

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