はじめに
現在Rustを勉強していますが配列やベクタ、スライスの違いに混乱したので
それぞれの特徴を整理してまとめました。
この記事では具体的な使い方よりもデータ構造を中心に解説します。
スタックとヒープ
配列やベクタの仕組みを理解するには、メモリの知識が必要になるので簡単に解説します。
メモリの領域には大きく分けてスタックとヒープの2種類があります。
※厳密には他にも領域はありますがここでは割愛します
スタックの特徴は以下の通りです。
-
ローカル変数や関数の引数等の割当先
-
LIFO(最後に追加したデータを最初に取り出す方式)
-
アクセス速度は速い
ヒープの特徴は以下の通りです。
-
大容量のデータや動的にサイズが決まるデータの割当先
-
アクセス速度は遅い
配列
配列の特徴は以下の通りです。
-
型は
[要素の型; 要素数]
-
固定長
-
コンパイル時にサイズが決まる
-
要素はスタックに格納される
配列を宣言した場合のメモリ配置は下図の通りです。
let a: [i32; 5] = [0, 1, 2, 3, 4];
ベクタ
ベクタの特徴は以下の通りです。
-
型は
Vec<要素の型>
-
可変長
-
実行時にサイズが決まる
-
要素はヒープに格納される
-
以下のデータを保持する
-
ヒープへのポインタ (ptr)
-
要素数 (len)
-
保持できるサイズ (capacity)
-
ベクタを宣言した場合のメモリ配置は下図の通りです。
ベクタ自体はスタックに格納されます。
let v: Vec<i32> = vec![0 ,1, 2, 3, 4];
スライス
スライスの特徴は以下の通りです。
-
型は
&[要素の型]
(可変は&mut [要素の型]
) -
配列やベクタへの参照
-
スライス自体は要素のデータ領域を持っていない
-
実行時にサイズが決まる
-
以下のデータを保持する
-
配列(またはベクタ)の要素へのポインタ (ptr)
-
要素数 (len)
-
スライスを宣言した場合のメモリ配置は下図の通りです。
スライス自体はスタックに格納されます。
配列のスライスならスタックの要素を参照します。
let a: [i32; 5] = [0, 1, 2, 3, 4];
let s: &[i32] = &a[1..4];
ベクタのスライスならヒープの要素を参照します。
let v: Vec<i32> = vec![0, 1, 2, 3, 4];
let s: &[i32] = &v[3..];
おわりに
自分は普段Pythonで書いているので、メモリについてはほとんど意識していませんでした。
ですがRustでプログラムを書くにはメモリ関係の知識がないと色々苦労します。
図を作成したおかげで理解がより深まった気がします。
参考文献