配列
同じ型の固定長の要素数を持つ型です。一度宣言するとサイズは変えられません。そのため、宣言時にサイズがわかっているケースで利用します。型定義は[型; サイズ]
で行います。
宣言
let arr: [i32; 3] = [1, 2, 3];
参照
println!("{}", arr[0]); // 1
他の言語と同様、サイズを超えたインデックスを与えるとプログラムは異常終了(パニック)します。
配列は宣言時に要素の型とサイズがわかっているので、トータルで必要なメモリサイズが計算できます。そのためデータはスタックに格納され、アクセスが高速になります。
タプル
固定長で複数の値をまとめあげる点では配列と似ていますが、各要素の型は必ずしも一致している必要はありません。
宣言
let tup: (i32, f64, &str) = (42, 3.14, "hello");
参照
println!("{}", tup.0); // 42
println!("{}", tup.1); // 3.14
println!("{}", tup.2); // hello
ベクタ
同じ型の可変長の要素を持ちます。配列と違い、宣言後に要素を追加したり削除したりできます。配列よりは低速度ですが、コレクションのサイズを柔軟に変えたい場合はベクタを使用します。
宣言
Vec::new
を使う方法とvec!
を使う方法があります。どちらも結果は同じです。
let mut vec1: Vec<i32> = Vec::new();
vec1.push(1);
vec1.push(2);
let vec2 = vec![3, 4, 5];
参照
[]
の中にインデックスを指定する方法とget
メソッドを使う方法があります。前者は配列同様パニックを起こす可能性がありますが、後者はOption
を返すのでその恐れはありません。
println!("{}", vec2[0]); // 3
if let Some(value) = vec2.get(1) {
println!("{}", value); // 4
}
更新
push
やpop
など、用途に応じたメソッドが利用できます。
vec1.push(6);
vec1.pop();
スライス
スライスはコレクションそのものではなく、あるコレクションの一部への参照を持つ変数です。すでに何かしらのコレクション(例えば配列)があったとして、そのコレクションの一部(全部も可能)を参照する形で宣言されます。
宣言時は&コレクション名[範囲]
の形で指定します。
let arr: [i32; 3] = [1, 2, 3];
let slice = &arr[0..2]; // [1, 2]
println!("{:?}", slice);
文字列スライス
スライスは様々なコレクションに対して作成でき、文字列もその対象です。文字列は&str
型として扱えるため、文字列の一部分を参照する場合は&str
に対するスライスを利用できます。
let s = "hello world";
let hello = &s[0..5];
let world = &s[6..];
println!("{}, {}", hello, world); // hello, world
ただし、このスライスの1要素はバイト単位であるため、マルチバイト文字を含む場合は「1要素=1文字」とは限りません。そのようなケースを想定するのであれば、for
文などを使って処理しましょう。
コレクションのまとめ
今回取り上げたコレクションの違いをまとめてみます。
種類 | 要素の型 | サイズ |
---|---|---|
配列 | すべて同じ | 固定 |
タプル | バラバラでもOK | 固定 |
ベクタ | すべて同じ | 可変 |
固定長と可変長で明確に分けているのでその点に注意しましょう。基本的にはベクタのほうが出番が多そうなイメージです。
TypeScriptのオブジェクトのように違う意味を持つ値をまとめて扱いたいが、それぞれに名前をつけるほどではない、みたいなケースでタプルが活用できそうです(TypeScriptにもタプルがありますが、そうやって使うイメージ)。
for
文で使うには
配列
そのままでは使えないので、参照を渡すかiter
メソッドを利用しましょう。
for val in &arr {
println!("{}", val);
}
タプル
iter
メソッドを利用します。
let tuples = [(1, "one"), (2, "two")];
for (num, word) in tuples.iter() {
println!("{}: {}", num, word);
}
ベクタ
そのままfor
文で使えますが、所有権が移動してしまうので、参照を渡しましょう。
for val in &vec2 {
println!("{}", val);
}
様々なメソッド
コレクション(正確に言えばIterator
トレイトを実装している型)にはいくつかのメソッドが用意されています。
そのうちの主なものを紹介します。
map
let vec2 = vec![3, 4, 5];
let doubled: Vec<i32> = vec2.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [6, 8, 10]
filter
let vec2 = vec![3, 4, 5];
let evens: Vec<i32> = vec2.iter().filter(|&&x| x % 2 == 0).cloned().collect();
println!("{:?}", evens); // [4]
fold
let vec2 = vec![3, 4, 5];
let sum: i32 = vec2.iter().fold(0, |acc, &x| acc + x);
println!("{}", sum); // 12
このあたりはTypeScriptと似ていますね(reduce
がRustではfold
になる?)。やはりコレクションから使えるメソッドは慣れると直感的に書けて筆者は好きです。