4

同じ初期化子で要素の大きな配列を初期化しようとしています。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

すべての初期化子をカット アンド ペーストせずにこの配列を初期化する方法はありますか?

4

3 に答える 3

5

問題は、マクロの展開が完全で独立して有効な文法要素でなければならないことです。a, bに拡張できる以上に拡張することはできません42 +。また、Rust では配列を (静的に) 連結またはコンスする方法もありません。配列初期化子全体を1ステップで展開する必要があります。

これは、プッシュダウン累積でマクロを使用して行うことができます。秘訣は、戻る途中で構築するのではなく、まだ構文的に有効でない部分配列式を再帰に渡すことです。展開の一番下まで到達すると、完成した表情を一斉に放つ。

長さ 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[..]);
}

ここでの戦略は、入力のサイズを 2 の累乗で乗算し、2 の累乗でない剰余を加算することです。これは、マクロの再帰の制限 (デフォルトは 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 , ] }
于 2016-03-28T09:32:13.053 に答える
3

これらのマクロの問題は、前者が Rust で有効な構文形式を生成しないことです。コンマで結合された 2 つの式は、それ自体では有効な形式ではありません。別のマクロの角括弧に「挿入」されているという事実は関係ありません。

率直に言って、通常の配列で適切に行う方法がわかりません。汎用パラメーターとして数値が存在しないことは、多くの有用なパターンを排除するよく知られた問題です。たとえば、それらがサポートされていれば、次のような関数を持つことができます。

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。一部の固定サイズの配列に似た APIを提供するarrayvecを試すこともできます。Vecそれを使用すると、次のようなことができます。

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]
}

以下も参照してください。

于 2016-03-28T08:31:33.270 に答える