はじめに
みなさん、Rustしてますか? Rustの配列って使い勝手が悪いですよね。
例えばStringの配列を作ろうとして、コンパイルが通らない経験とかあると思います。
そんな、配列の初期化方法について踏み込んだ記事が見つからなかったので、まとめてみました。
配列の使いにくさに関する解説記事も書けたら書きたいな…。
配列の代わりにVecを使う
前提を覆すようですが、『Rustで配列を使え』は全て詐欺です。Vecを使いましょう。
Vecは配列の上位互換です。配列で出来ることはVecでも出来ますし、オーバヘッドが問題となることも普通は無いはずです。Vecを使いましょう。
Vecの初期化方法について、例を3つだけ示しておきます。ここで語る内容でもない気がする…。
// マクロで初期化
let a = vec!["a".to_string(); 10];
// pushで初期化
let mut b = Vec::with_capacity(10);
b.push(1);
b.push(2);
b.push(3);
// イテレータで初期化
let c: Vec<i32> = (0..100).collect();
やむを得ず配列を使用する場合
のっぴきならない事情によって配列を使いたいこともあるでしょう。何事にも例外は付き物ですから。
そんなときにオススメなのは、Vecからの変換です。これ一つだけ覚えておけば大抵は対応できます。
use std::convert::TryInto;
// 1.48.0 以降
// Vecの大きさがNでない場合は、unwrapで落ちる。
let output: [T; N] = input.try_into().unwrap();
まとめ
- 『Rustで配列を使え』は全て詐欺。代わりにVecを使う。
- 配列はVecを変換して生成する。
- 『Rustで配列を使え』は(ry
おまけ
記事の内容が物足りなかった人の為に、配列の初期化方法をいくつか紹介します。
ヒープ領域に生成
直接Boxで包んで生成することで、スタック領域を介さずに初期化出来ます。
use std::convert::TryInto;
// 1.43.0 以降
let output: Box<[T; N]> = input.into_boxed_slice().try_into().unwrap();
イテレータから生成する
イテレータ→Vec→配列のように、一旦Vecに変換します。特に新しいことはありません。
use std::convert::TryInto;
let tmp: Vec<T> = input.take(N).collect();
// 1.48.0 以降
let output: [T; N] = tmp.try_into().unwrap();
ヒープ領域の場合
use std::convert::TryInto;
let tmp: Box<[T]> = input.take(N).collect();
// 1.43.0 以降
let output: Box<[T; N]> = tmp.try_into().unwrap();
コンパイル時定数から生成する
1.38でうっかり有効化され、1.50で事後承認された曰く付きの機能だそうです。
// 1.50 以降?
const INIT_VEC: Vec<T> = Vec::new();
let data_bin = [INIT_VEC; N];
// 1.50 以降?
const INIT_VAL: Option<T> = None;
let lazy_array = [INIT_VAL; N];
// 1.50 以降?
let str_array = {
const INIT_VAL: String = String::new();
[INIT_VAL; N]
};
窓として参照を生成する
配列の実体が本当に必要でしたか? 参照だけで良い場合、長い配列やVecの一部を窓のように切り取ってアクセスすることが出来ます。
use std::convert::TryInto;
let tmp: &mut [T] = &mut input[0..N];
// 1.34.0 以降
let output: &mut [T; N] = tmp.try_into().unwrap_or_else(|_| unreachable!());