Rustのベクタ(Vec)って聞いたことあるけど、配列と何が違うの?どうやって使うの?
そんな疑問で頭がいっぱいになっていませんか?
この記事では、Rustを始めたばかりの方でも迷わないように、ベクタの基本から、実際にプログラムで動かすためのコード例、ちょっとしたコツや気をつけるべき点まで、詳しく解説していきます。
この記事でわかること
- ベクタがどんなものか、配列との違いがわかる
- ベクタの作り方を覚えられる
- ベクタのデータを読んだり、書き換えたりできるようになる
- ベクタにデータを追加したり、削除したりする方法が身につく
- ベクタの中身を順番に処理する方法をマスターできる
- ベクタを使うときに気をつけるポイントが理解できる
Rustのベクタとは?配列との違いも理解しよう
まず、Rustのベクタ(`Vec<T>`)が何者なのか、ざっくり掴んでおきましょう!
一言でいうと、プログラム実行中にサイズ(要素数)を変えられるリストのようなものです。他のプログラミング言語でいう「動的配列」や「リスト」に近いイメージですね。
Rustには「配列」もありますが、こちらは一度作るとサイズを変えられません。例えば、最初に要素を5つ持つ配列を作ったら、ずっと要素数は5つのままです。
一方、ベクタはもっと柔軟!最初は空っぽでも、後からデータをどんどん追加できますし、逆に減らすことも可能です。データを一時的にたくさん溜め込みたい時や、いくつデータが必要になるか事前にわからない場合に、ベクタはとっても役立ちます。
なぜRustでベクタがよく使われるかというと、Rustがメモリを安全に管理する仕組み(所有権とか、ちょっと難しい話!)とも相性が良いからなんです。
まあ、今は「サイズが変えられる便利なリストなんだな」くらいに思っておけばOKです。
Rustのベクタの基本的な作り方(宣言と初期化)
では、実際にベクタを作ってみましょう!作り方は主に2パターンあります。どちらも簡単なので、サクッと覚えちゃいましょう。
空のベクタを作成する (`Vec::new`)
最初は中身が何もない、空っぽのベクタを作りたい場合がありますよね。そんなときは `Vec::new()` を使います。
ただし、この方法だと、ベクタにどんな種類のデータを入れるのか、Rustコンパイラが判断できません。なので、変数の型を明示的に教えてあげる必要があります。
(後で `push` などで要素を追加すれば、型が決まる場合もあります)
fn main() { // i32型(32ビット整数)の要素を入れる空のベクタを作成 let mut v: Vec<i32> = Vec::new(); // あとから要素を追加できる v.push(10); v.push(20); v.push(30); println!("vの中身: {:?}", v); // デバッグ出力 }
表示結果
vの中身: [10, 20, 30]
ここで `let mut v` のように `mut` を付けているのは、後で `v.push()` を使ってベクタの中身を変更する(mutableにする)ためです。`mut` がないと、要素を追加しようとしたときにエラーになりますよ。
初期値を持つベクタを作成する (`vec![]` マクロ)
最初からいくつかデータが入った状態のベクタを作りたいなら、`vec![]` マクロを使うのが断然ラクチンです!角括弧 `[]` の中に、入れたい要素をカンマ区切りで並べるだけ。
この方法なら、Rustコンパイラが入れた要素から型を推測してくれるので、型を明示的に書かなくても良い場合が多いです。便利!
fn main() { // 整数が入ったベクタを作成 (型はi32と推論される) let v1 = vec![1, 2, 3, 4, 5]; println!("v1の中身: {:?}", v1); // 文字列スライスが入ったベクタを作成 (型は&strと推論される) let v2 = vec!["apple", "banana", "orange"]; println!("v2の中身: {:?}", v2); // 要素が1つだけでもOK let v3 = vec![100]; println!("v3の中身: {:?}", v3); // もちろん空のベクタも作れるけど、型が必要な場合がある // let v4 = vec![]; // これは型が不明でエラーになる可能性がある let v4: Vec<i32> = vec![]; // 型を指定すればOK println!("v4の中身: {:?}", v4); }
表示結果
v1の中身: [1, 2, 3, 4, 5] v2の中身: ["apple", "banana", "orange"] v3の中身: [100] v4の中身: []
どうです? `vec![]` マクロ、とっても簡単でしょ?普段はこちらを使うことが多いかもしれませんね。
Rustのベクタの要素にアクセス・変更する方法
ベクタを作ったら、次はその中身を見たり、書き換えたりしたくなりますよね。要素へのアクセスも簡単です!
一番基本的なのは、配列と同じようにインデックス(番号)を使う方法です。インデックスは `0` から始まることに注意してくださいね。ベクタ `v` の最初の要素は `v[0]`、2番目は `v[1]` という具合です。
fn main() { let mut v = vec![10, 20, 30, 40, 50]; // インデックスを使って要素にアクセス (読み取り) let first_element = v[0]; // 最初の要素 (10) let third_element = v[2]; // 3番目の要素 (30) println!("最初の要素: {}", first_element); println!("3番目の要素: {}", third_element); // インデックスを使って要素を変更 v[1] = 25; // 2番目の要素を20から25に変更 println!("変更後のv: {:?}", v); // 範囲外のインデックスにアクセスしようとすると…? // let sixth_element = v[5]; // これはエラー (panic!) になります! // println!("6番目の要素: {}", sixth_element); }
表示結果
最初の要素: 10 3番目の要素: 30 変更後のv: [10, 25, 30, 40, 50]
上のコードの最後でコメントアウトしている部分のように、ベクタの範囲外のインデックス(例: 要素数が5なのに `v[5]` にアクセス)を指定すると、プログラムが `panic` というエラーを起こして強制終了してしまいます。これは結構ありがちなミスなので気をつけましょう!
じゃあ、安全にアクセスするにはどうすればいいの? という疑問には、`get()` メソッドが答えてくれます。
`get()` はインデックスを引数に取り、その位置に要素があれば `Some(要素の値)` を、なければ `None` を返します。
これは `Option` という型で、「値があるかもしれないし、ないかもしれない」状態を表します。`panic` する代わりに、値があるかどうかをチェックできるので安全ですね。
fn main() { let v = vec![10, 20, 30]; // get() を使って安全にアクセス let second = v.get(1); // インデックス1 (2番目の要素) を取得 match second { Some(value) => println!("2番目の要素は: {}", value), None => println!("2番目の要素はありません。"), } let fifth = v.get(4); // 範囲外のインデックス4を取得 match fifth { Some(value) => println!("5番目の要素は: {}", value), None => println!("5番目の要素はありません。"), } // if let を使うともっと簡潔に書けることも if let Some(value) = v.get(0) { println!("if let で取得: 最初の要素は {}", value); } }
表示結果
2番目の要素は: 20 5番目の要素はありません。 if let で取得: 最初の要素は 10
`[]` で直接アクセスするか、`get()` で安全にアクセスするかは、状況に応じて使い分けるのが良いでしょう。
Rustのベクタに要素を追加・削除する方法
ベクタの真骨頂は、なんといっても要素を自由に追加したり削除したりできること!そのための基本的なメソッドを見ていきましょう。
要素を末尾に追加する (`push`)
ベクタの一番最後に新しい要素を追加したいときは、`push` メソッドを使います。これは非常によく使う操作ですよ!
`push` を使うには、ベクタが `mut` (変更可能) である必要があります。
fn main() { let mut v = vec![1]; // 最初に要素1だけを持つベクタ println!("push前: {:?}", v); v.push(2); // 末尾に2を追加 println!("push後 (2追加): {:?}", v); v.push(3); // 末尾に3を追加 println!("push後 (3追加): {:?}", v); }
表示結果
push前: [1] push後 (2追加): [1, 2] push後 (3追加): [1, 2, 3]
ね、簡単でしょ? `push` でどんどん要素を増やせます。
要素を末尾から削除する (`pop`)
逆に、ベクタの一番最後の要素を取り除きたい(削除したい)場合は、`pop` メソッドを使います。
`pop` もベクタを変更するので、ベクタは `mut` である必要があります。
`pop` は、取り除いた要素を `Option` 型で返します。ベクタが空でなければ `Some(最後の要素)` を、空なら `None` を返します。
fn main() { let mut v = vec![10, 20, 30]; println!("pop前: {:?}", v); let last_element = v.pop(); // 末尾の要素 (30) を削除して取得 println!("popで取得した要素: {:?}", last_element); // Some(30) が表示されるはず println!("pop後 (1回目): {:?}", v); let second_last_element = v.pop(); // 末尾の要素 (20) を削除して取得 println!("popで取得した要素: {:?}", second_last_element); // Some(20) println!("pop後 (2回目): {:?}", v); v.pop(); // 末尾の要素 (10) を削除 println!("pop後 (3回目): {:?}", v); // これで空になる let empty_element = v.pop(); // 空のベクタに対してpop println!("空のベクタからpop: {:?}", empty_element); // None が表示されるはず }
表示結果
pop前: [10, 20, 30] popで取得した要素: Some(30) pop後 (1回目): [10, 20] popで取得した要素: Some(20) pop後 (2回目): [10] pop後 (3回目): [] 空のベクタからpop: None
`push` と `pop` はセットで覚えておくと便利ですよ!
ちなみに、特定の位置に要素を挿入する `insert()` や、特定の位置の要素を削除する `remove()` というメソッドもありますが、まずは `push` と `pop` をしっかりマスターしましょう。
Rustのベクタを繰り返し処理 (イテレーション) する方法
ベクタの中身を一つずつ順番に取り出して何か処理をしたい、ということはよくあります。
例えば、ベクタの全要素を画面に表示したり、合計値を計算したり。このような繰り返し処理を「イテレーション」と呼びます。
Rustでは `for` ループを使うのが一般的です。ベクタのイテレーションには、主に3つの方法があります。
1. 要素の参照 (`&T`) を取得する (`iter()` を使う)
元のベクタや要素の値を変更せず、ただ読み取りたいだけの場合に使います。これが一番安全でよく使われる方法かもしれません。
fn main() { let v = vec!["a", "b", "c"]; // iter() を使って各要素への参照を取得 for element in v.iter() { println!("参照: {}", element); // *element = "z"; // ここで変更しようとするとエラー!参照は変更不可 } // ループの後も v は使える println!("ループ後のv: {:?}", v); }
表示結果
参照: a 参照: b 参照: c ループ後のv: ["a", "b", "c"]
2. 要素のミュータブルな参照 (`&mut T`) を取得する (`iter_mut()` を使う)
ループの中で要素の値を変更したい場合に使います。ベクタ自体が `mut` である必要があります。
fn main() { let mut v = vec![100, 200, 300]; // iter_mut() を使って各要素へのミュータブルな参照を取得 for element in v.iter_mut() { *element += 10; // 参照先の値を変更 (* が必要) println!("変更中: {}", element); } println!("ループ後のv: {:?}", v); }
表示結果
変更中: 110 変更中: 210 変更中: 320 ループ後のv: [110, 210, 320]
`*element` のようにアスタリスク `*` を付けて、参照先の値にアクセスして変更していますね。
3. 要素の所有権を移動する (`into_iter()` または直接 `for elem in v`)
ループの中で各要素の所有権を完全に取得したい場合に使います。この方法を使うと、ループが終わった後、元のベクタ `v` は使えなくなってしまうので注意が必要です!
fn main() { let v = vec![String::from("hello"), String::from("world")]; // ベクタ v の所有権がループに移る for element in v { // .into_iter() は暗黙的に呼ばれることが多い println!("所有権取得: {}", element); // ここで element (String) を自由にいじれる } // ループの後、v はもう使えない! // println!("ループ後のv: {:?}", v); // これはコンパイルエラー! }
表示結果
所有権取得: hello 所有権取得: world
どのイテレーション方法を使うかは、ループの中で要素をどう扱いたいか(読むだけ?変更したい?所有したい?)によって決まります。最初は `iter()` から試してみるのが良いかもしれませんね。
Rustのベクタを使う上での注意点
ベクタはとても便利ですが、いくつか気をつけておきたい点があります。これを知っておくと、予期せぬエラーを防いだり、よりスムーズにプログラミングを進めたりできますよ。
範囲外アクセスに注意!
これは先ほども触れましたが、`v[index]` の形で要素にアクセスするとき、`index` がベクタの範囲を超えているとプログラムが `panic` します。所有権と借用ルールを意識しよう
Rustには「所有権」や「借用」という、メモリ安全性を保つための独特なルールがあります。ベクタを使うときも、これらのルールが関係してきます。例えば、あるベクタへの「変更可能な参照(`&mut T`)」を持っている間は、同じベクタへの他の参照(変更可能でも不可能でも)を持つことができません。`for` ループで `iter_mut()` を使っている最中に、そのベクタに `push` しようとしたりすると、コンパイラに怒られることがあります。
また、ベクタを単純に関数に渡すと、所有権が関数に移ってしまい、元の場所では使えなくなることがあります。これを避けたい場合は、「参照(`&Vec
要素追加時のメモリ再確保(ちょっとだけ気にする程度でOK)
ベクタはサイズが可変ですが、内部的にはある程度のメモリ領域をあらかじめ確保しています。`push` で要素を追加していき、その確保した領域がいっぱいになると、ベクタはもっと大きな新しいメモリ領域を探し、そこに今までの全要素をコピーしてから、新しい要素を追加します。この「再確保」の処理は、ほんの少しだけ時間がかかることがあります。ものすごくパフォーマンスが求められる場面以外では、ほとんど気にする必要はありません。
ただ、もし最初から「だいたいこれくらいの要素数になりそうだな」と予想がつく場合は、`Vec::with_capacity(予想数)` という関数で、最初に十分な領域を確保しておくと、途中の再確保を減らせて少し効率が良くなる、なんてテクニックもあります。
これらの注意点は、使いながら徐々に体験していくのが一番です。怖がらずに、どんどんベクタを使ってみてください!
【まとめ】Rustのベクタを使いこなして開発を加速しよう!
お疲れ様でした!この記事では、Rustのパワフルなコレクション型である「ベクタ(`Vec<T>`)」について、基本から応用まで一通り見てきましたね。
ポイントを振り返ってみましょう。
- ベクタはサイズ(要素数)を自由に変えられるリスト。
- `Vec::new()` や `vec![]` で簡単に作れる。
- インデックス `[]` や `get()` で要素にアクセス・変更できる。
- `push` で末尾に要素を追加、`pop` で末尾から削除できる。
- `for` ループと `iter()`, `iter_mut()`, `into_iter()` で繰り返し処理ができる。
- 範囲外アクセスや所有権ルールには少し注意が必要。
ベクタは、Rustプログラミングの様々な場面で登場するとても基本的なデータ構造です。ファイルから読み込んだデータを保持したり、ユーザーの入力を一時的に保存したり、計算結果をまとめたり…使いこなせれば、あなたの書くプログラムの表現力が格段にアップするはず!
今回学んだことを土台にして、ぜひ実際のコードでベクタを使ってみてください。もしわからないことが出てきても、公式ドキュメント(The Rust Programming Language、通称 The Book)などを参考にすれば、きっと解決策が見つかります。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。