【Rust】配列を初期化するn通りの方法と制約

2020-03-19

ちょっと前からRustを触り始めて、ようやく多少動かせるようになってきた。 で今までわかった範囲で、配列の初期化についてまとめてみた。

リテラル

Rustで配列を記述するには、型を [要素の型; 要素数] という具合に指定する。 で初期化の要素をブラケット内 [ ] に記述する:

let array: [i32; 3] = [1, 2, 3];  // 1, 2, 3 のi32型3要素の配列

Rust Playgroundで実行

デフォルト値で埋める

要素数が多い場合、初期化時にすべての要素の初期値を列挙するのが大変。 デフォルト値で初期化したい場合、要素数がそこまで多くなければ Default::default() というのが使える:

let array2: [i32; 32] = Default::default();  // 0が32個

Rust Playgroundで実行

使用できる個数に制限があって、 32個以下 でしか使えない。 なんでそんな制約があるんだと思うんだけど、マクロで実装されているからなのかもしれない。

一定の値で埋める

要素の型が Copy トレイトを実装している場合、全要素を同じ値で初期化するなら [要素; 要素数] と書ける:

let array3: [i32; 33] = [1; 33];

Rust Playgroundで実行

この場合には32個以上も可能。

個別に初期化したい場合

リテラルでの直接記述や同じ値での初期化じゃなく、インデクスを使ってとか乱数を使ってとかで初期化したい場合どうするか。 Rustは安全性が最優先の言語で、変数が未初期化の状態を許さないので、でかい配列といえど確保のみということはできず、要素もすべて宣言時に初期化済みの状態になっていないといけない。

ではどうするか。 実のところ、 unsafe を使うことになる:

use std::mem::MaybeUninit;

fn func() {
const LEN: usize = 16; // 確保したい要素数
// 未初期化の領域を確保
let mut array4: [MaybeUninit<i32>; LEN] = unsafe { MaybeUninit::uninit().assume_init() };
for (i, slot) in array4.iter_mut().enumerate() {
// [i]の要素を初期化する
*slot = MaybeUninit::new((i * i) as i32);
}
// MaybeUninit を外した安全な配列型に変換
let array4: [i32; LEN] = unsafe { std::mem::transmute::<_, [i32; LEN]>(array4) };
...

Rust Playgroundで実行

MaybeUninit で要素が未初期化の状態の配列を確保し、 各要素を初期化した後、 std::mem::transmute で型を変換して取り出す。

「え、そんな基本的なことで unsafe を使わないといけないの?」と思うのだけど、そういうものらしい。

crate を利用する

上の MaybeUninit を使う方法は決まった形式なので、モジュール化できれば簡潔に利用できて便利。 crates.io にいろいろあるみたいなんだけど、 array-macro というものを使ってみた。

Cargo.toml の [dependencies] に追加:

[dependencies]
array-macro = "1.0.4"

して、利用

use array_macro::*;

let array5: [i32; LEN] = array![|i| i * i; LEN];
let array6: [i32; LEN] = array![1; LEN];

できる。 クロージャを渡す方法以外にも通常の固定値での初期化と同じようにも書けるが、こちらは Copy トレイトじゃない型でも使用できるという違いがある。

静的な配列の個数指定

静的な配列も定義できる。 ローカル変数の場合は型宣言を省略できるが、静的な場合には明示する必要がある:

pub const ARRAY: [i32; 3] = [1, 2, 3];

開発時にコードの修正で要素数を増減させる場合に、個数宣言(; 3の箇所)も合わせないといけなくて要素数に応じて修正する必要があるのが地味に面倒だ。

でcrateがないか調べたらあった:counted-array (from Array length inference?)。 これを使えば個数に _ を指定できて、自動的にサイズを埋めてくれる:

counted_array!(pub const ARRAY2: [i32; _] = [1, 2, 3]);

雑感

コアな言語仕様だけ用意してマクロでなんとかするというのも理にかなっているといえばそうなんだけど、 「このcrateを使えば簡単にできますよ」というのは言語に慣れてない人間にとっては探すのも難しいのでなかなか辛いと思った。