Rustのベクトル(Vec):動的配列の作成、削除、変更、および照会
Vec<T>(vector) は、Rust 標準ライブラリで最も一般的に使用される動的配列です。実行時にサイズを拡大・縮小することができ、すべてのデータはヒープ上に割り当てられます。これは、Rust における「ArrayList」や「Python のリスト」に相当するものです。
配列が「固定サイズのロッカー」のようなものだとすれば、Vecは「拡張可能な倉庫」のようなものです。スペースが足りなくなると自動的に拡張されるため、手動でメモリを管理する必要がありません。
1. 学習内容
vec!マクロとVec::new()を使用して動的配列を作成する- push/pop を使って、要素を末尾に追加または削除する
insertおよびremoveを使用して、中央の要素を追加または削除します- 容量と長さの違いを理解する
- Vecs の走査と反復処理、および Vecs と配列間の変換
2. スーパーのレジ係の物語
(1) もどかしさ:ショッピングカートの大きさがわからないこと
トムはスーパーマーケットのレジシステムを開発しています。顧客の買い物かごには:
// The problem with arrays -- you must know how many items in advance!
let cart: [&str; 3] = ["milk", "bread", "eggs"];
// Customer says: "Add one more cola"
// Arrays can't grow dynamically -- you need to redeclare...
let mut cart2 = ["milk", "bread", "eggs", "cola"];
// Customer: "Remove eggs, add butter instead"
// Modify every time? This is painful!
配列のサイズはコンパイル時に固定されます。しかし、ショッピングカートの場合、顧客が3個買うのか30個買うのかはわかりません。そのため、実行時に動的に拡張できるデータ構造が必要となります。
(2) RustのVecに対する解決策
fn main() {
// Start with an empty cart
let mut cart: Vec<&str> = Vec::new();
// Customer adds items one by one
cart.push("milk");
cart.push("bread");
cart.push("eggs");
println!("Cart has {} items: {:?}", cart.len(), cart);
// Customer adds more
cart.push("cola");
println!("Added cola: {:?}", cart);
// Customer removes an item
cart.pop();
println!("Removed last: {:?}", cart);
// Check what's inside
println!("Current cart: {:?}", cart);
}
Vec::new()空の動的配列を作成します。push要素を追加します(配列のサイズは自動的に調整されます)。pop最後の要素を削除します。サイズを指定する必要はありません。Vec はヒープ上のメモリを自動的に管理します。
3. 動的配列 Vec
(1) コンセプトの概要
graph LR
A[Vec<T> dynamic array] --> B[Creation]
A --> C[CRUD operations]
A --> D[Capacity management]
A --> E[Conversion]
B --> B1[Vec::new()]
B --> B2[vec! macro]
B --> B3[collect()]
C --> C1[push / pop]
C --> C2[insert / remove]
C --> C3[index / .get()]
D --> D1[capacity: allocated]
D --> D2[len: actual use]
D --> D3[shrink_to_fit]
E --> E1[From array]
E --> E2[Back to array]
(2) ベクトルと配列の比較
| 次元 | 配列 [T; N] |
ベクトル Vec<T> |
|---|---|---|
| サイズ | コンパイル時に固定 | 実行時に動的に増加 |
| 割り当て場所 | スタック(通常) | ヒープ |
| 拡大・縮小 | 未対応 | 自動で2倍に拡大 |
| APIの充実度 | 限定的 | 極めて充実 |
| アクセス速度 | 極めて高速(スタック上で連続) | 極めて高速(ヒープ上で連続) |
| ユースケース | 固定サイズ、スタック上に割り当てられる | 数が不定、頻繁な追加・削除 |
| パフォーマンス上のオーバーヘッド | なし | push により再割り当てが発生する可能性がある |
(3) 一般的なVecメソッドのクイックリファレンス
| メソッド | 戻り値の型 | 説明 | 時間計算量 |
|---|---|---|---|
push(val) |
() |
末尾への追加 | O(1)(平均時間) |
pop() |
Option<T> |
テール・ポップアップ | O(1) |
insert(idx, val) |
() |
指定した位置への挿入 | O(n) |
remove(idx) |
T |
指定した位置で削除 | O(n) |
get(idx) |
Option<&T> |
セキュアアクセス | O(1) |
len() |
usize |
要素の数 | O(1) |
capacity() |
usize |
割り当て容量 | O(1) |
clear() () O(n) でクリア |
|||
contains(&val) |
bool |
含む | O(n) |
sort() |
() |
インプレースソート | O(n log n) |
dedup() |
() |
重複を削除(事前にソートが必要) | O(n) |
retain(f) |
() |
条件を満たす要素を保持する | O(n) |
shrink_to_fit() |
() |
余剰容量を解放する | — |
(4) ベクトル反復法の比較
| メソッド | 構文 | 所有権 | Vec で利用可能 |
|---|---|---|---|
| 借用された反復 | for x in &v |
読み取り専用参照 | 利用可能 |
| 可変引数によるトラバーサル | for x in &mut v |
可変引数による参照 | 利用可能(修正済み) |
| 消費によるトラバーサル | for x in v |
所有権の移転 | 利用不可 |
| イテレータ | v.iter() |
読み取り専用参照 | 利用可能 |
| 列挙の反復処理 | v.iter().enumerate() |
読み取り専用参照 + インデックス | 利用可能 |
4. 例
(1) ▶ サンプル:vec! マクロと push/pop (難易度 ⭐)
// ============================================
// Vec: vec! macro, push, pop, len, is_empty
// ============================================
fn main() {
// Method 1: Vec::new()
let mut v1: Vec<i32> = Vec::new();
v1.push(10);
v1.push(20);
v1.push(30);
println!("v1: {:?}, len={}", v1, v1.len());
// Method 2: vec! macro (most common)
let mut v2 = vec!["apple", "banana", "cherry"];
println!("v2: {:?}, len={}", v2, v2.len());
// Method 3: vec! with repeated value
let v3 = vec![0; 5];
println!("v3 (five zeros): {:?}", v3);
// push: add to the end
v2.push("date");
println!("After push: {:?}", v2);
// pop: remove from the end
let last = v2.pop();
println!("Popped: {:?}", last);
println!("After pop: {:?}", v2);
// is_empty
println!("v2 empty? {}", v2.is_empty());
// Access by index
println!("v2[0] = {}", v2[0]);
// len
println!("v2 length: {}", v2.len());
}
出力:
v1: [10, 20, 30], len=3
v2: ["apple", "banana", "cherry"], len=3
v3 (five zeros): [0, 0, 0, 0, 0]
After push: ["apple", "banana", "cherry", "date"]
Popped: Some("date")
After pop: ["apple", "banana", "cherry"]
v2 empty? false
v2[0] = apple
v2 length: 3
vec!はリストを作成する最も一般的な方法です。リストにはvec!["a", "b", "c"]またはvecを指定します。pushはリストの末尾に要素を追加し、popはリストの末尾から要素を取り出します(Option<T>を返します)。len()はリストに含まれる要素の数を返します。
(2) ▶ サンプル:挿入・削除と容量管理(難易度 ⭐⭐)
// ============================================
// Vec: insert, remove, capacity vs length
// ============================================
fn main() {
let mut cart: Vec<&str> = Vec::with_capacity(3);
// capacity vs length
println!("Initial -- len: {}, cap: {}", cart.len(), cart.capacity());
cart.push("milk");
cart.push("bread");
cart.push("eggs");
println!("After 3 pushes -- len: {}, cap: {}", cart.len(), cart.capacity());
// insert at arbitrary position
cart.insert(1, "cola"); // insert "cola" at index 1
println!("After insert at 1: {:?}", cart);
println!("Now -- len: {}, cap: {}", cart.len(), cart.capacity());
// Capacity may have doubled!
// remove at arbitrary position
let removed = cart.remove(2); // remove element at index 2
println!("Removed: {}", removed);
println!("After remove at 2: {:?}", cart);
// get -- safe access (returns Option<&T>)
match cart.get(0) {
Some(item) => println!("First item: {}", item),
None => println!("Empty cart!"),
}
// Try an out-of-bounds index with get (safe)
match cart.get(99) {
Some(item) => println!("Item at 99: {}", item),
None => println!("Index 99 is out of bounds!"),
}
// shrink_to_fit: reduce capacity to match length
cart.shrink_to_fit();
println!("After shrink -- len: {}, cap: {}", cart.len(), cart.capacity());
}
出力:
Initial -- len: 0, cap: 3
After 3 pushes -- len: 3, cap: 3
After insert at 1: ["milk", "cola", "bread", "eggs"]
Now -- len: 4, cap: 6
Removed: bread
After remove at 2: ["milk", "cola", "eggs"]
First item: milk
Index 99 is out of bounds!
After shrink -- len: 3, cap: 3
insert(idx, val)は指定された位置に挿入し(後続の要素を右にずらす)、remove(idx)は指定された位置の要素を削除してそれを返す。capacityは Vec に割り当てられたメモリ量(lenより大きくなる場合がある)であり、shrink_to_fit()は余分な領域を解放する。.get()安全なアクセスを保証します。範囲外のアクセスが発生した場合、パニックを引き起こす代わりにNoneを返します。
(3) ▶ サンプル:ベクトルの走査と反復処理の方法 (難易度 ⭐⭐)
// ============================================
// Vec: iteration, mutation, and functional methods
// ============================================
fn main() {
let mut prices = vec![29.9, 49.9, 15.5, 99.0, 8.8];
// Method 1: for-in by reference
print!("Prices: ");
for p in &prices {
print!("{:.1} ", p);
}
println!();
// Method 2: mutable iteration (add tax)
for p in &mut prices {
*p *= 1.1; // 10% tax
}
println!("With 10% tax: {:?}", prices);
// Method 3: .iter().enumerate()
for (i, p) in prices.iter().enumerate() {
println!("Item {}: {:.2}", i, p);
}
// Method 4: functional style -- map, filter, sum
let total: f64 = prices.iter().sum();
println!("Total: {:.2}", total);
let above_30: Vec<f64> = prices.iter()
.filter(|&&p| p > 30.0)
.copied()
.collect();
println!("Items above 30: {:?}", above_30);
let discounted: Vec<f64> = prices.iter()
.map(|p| p * 0.8) // 20% off
.collect();
println!("20% discount: {:?}", discounted);
}
出力:
Prices: 29.9 49.9 15.5 99.0 8.8
With 10% tax: [32.89, 54.89, 17.05, 108.9, 9.68]
Item 0: 32.89
Item 1: 54.89
Item 2: 17.05
Item 3: 108.90
Item 4: 9.68
Total: 223.41
Items above 30: [32.89, 54.89, 108.9]
20% discount: [26.312, 43.912, 13.640000000000002, 87.12, 7.744]
Vec は複数の反復処理メソッドをサポートしています。読み取り専用の反復処理には
&v、要素の変更には&mut vを使用します。関数型スタイルのメソッドである.iter()、.map()、.filter()、および.sum()により、データ処理を簡潔かつ洗練されたものにすることができます。.collect()は、イテレータを再び Vec に変換します。
(4) ▶ サンプル:Vec と配列の相互変換 (難易度 ⭐⭐)
// ============================================
// Conversion between Vec and arrays
// ============================================
fn main() {
// Array -> Vec: via .to_vec()
let arr: [i32; 5] = [10, 20, 30, 40, 50];
let vec_from_arr: Vec<i32> = arr.to_vec();
println!("Array to Vec: {:?}", vec_from_arr);
// Array -> Vec: via .iter().copied().collect()
let vec_via_iter: Vec<i32> = arr.iter().copied().collect();
println!("Via iter: {:?}", vec_via_iter);
// Vec -> Array: via try_into() (returns Result)
let vec_data = vec![1, 2, 3, 4];
// let bad_array: [i32; 5] = vec_data.try_into().unwrap(); // PANIC: length mismatch
let ok_array: [i32; 4] = vec_data.try_into().unwrap();
println!("Vec to array: {:?}", ok_array);
// Vec -> slice (zero-cost, no copy)
let slice: &[i32] = &ok_array[1..3];
println!("Slice of array: {:?}", slice);
// Vec from iterator
let squares: Vec<i32> = (1..=5).map(|x| x * x).collect();
println!("Squares: {:?}", squares);
// Vec from repeated value
let repeated = vec!["hello"; 3];
println!("Repeated: {:?}", repeated);
}
出力:
Array to Vec: [10, 20, 30, 40, 50]
Via iter: [10, 20, 30, 40, 50]
Vec to array: [1, 2, 3, 4]
Slice of array: [20, 30]
Squares: [1, 4, 9, 16, 25]
Repeated: ["hello", "hello", "hello"]
.to_vec()を使用すると、配列を Vec に変換できます。.try_into().unwrap()を使用すると、Vec を配列に変換できます。この際、長さが一致していないとパニックが発生します。Vec は&[T]を使用することで、コストなしでスライスに変換できます。.collect()を使用してイテレータから Vec を作成するのは、よく使われる手法です。
(5) ▶ サンプル:題 5:総合演習—ショッピングカートと価格計算(難易度 ⭐⭐⭐)
// ============================================
// Comprehensive Example: Vec Insert, Delete, Update, Query + Functional Iteration
// ============================================
#[derive(Debug, Clone)]
struct Item {
name: String,
price: f64,
quantity: u32,
}
impl Item {
fn new(name: &str, price: f64, quantity: u32) -> Self {
Item { name: name.to_string(), price, quantity }
}
fn subtotal(&self) -> f64 {
self.price * self.quantity as f64
}
}
fn print_cart(cart: &[Item]) {
println!("{:<15} {:>8} {:>6} {:>10}", "Products", "Unit Price", "Quantity", "Subtotal");
println!("{}", "-".repeat(42));
for item in cart {
println!("{:<15} {:>8.2} {:>6} {:>10.2}",
item.name, item.price, item.quantity, item.subtotal());
}
let total: f64 = cart.iter().map(|i| i.subtotal()).sum();
println!("{}", "-".repeat(42));
println!("{:<15} {:>8} {:>6} {:>10.2}", "Total", "", "", total);
}
fn main() {
let mut cart: Vec<Item> = Vec::new();
cart.push(Item::new("Milk", 5.5, 2));
cart.push(Item::new("Bread", 8.0, 1));
cart.push(Item::new("Eggs", 12.5, 3));
cart.push(Item::new("Cola", 3.0, 4));
println!("=== Shopping Cart ===");
print_cart(&cart);
cart.retain(|i| i.name != "Cola");
println!("\n--- Remove Cola after ---");
print_cart(&cart);
cart.push(Item::new("Butter", 15.0, 2));
if let Some(eggs) = cart.iter_mut().find(|i| i.name == "Eggs") {
eggs.quantity += 2;
}
println!("\n--- Add Butter + Add Eggs Quantity ---");
print_cart(&cart);
let total: f64 = cart.iter().map(|i| i.subtotal()).sum();
let tax = total * 0.08;
println!("\nBefore taxes: {:.2}, Tax(8%): {:.2}, Total due: {:.2}", total, tax, total + tax);
let expensive: Vec<&Item> = cart.iter().filter(|i| i.price > 10.0).collect();
println!("Unit Price > 10 Items priced at yuan: {:?}", expensive.iter().map(|i| &i.name).collect::<Vec<_>>());
}
出力:
=== Shopping Cart ===
Products Unit Price Quantity Subtotal
------------------------------------------
Milk 5.50 2 11.00
Bread 8.00 1 8.00
Eggs 12.50 3 37.50
Cola 3.00 4 12.00
------------------------------------------
Total 68.50
--- Remove Cola after ---
Products Unit Price Quantity Subtotal
------------------------------------------
Milk 5.50 2 11.00
Bread 8.00 1 8.00
Eggs 12.50 3 37.50
------------------------------------------
Total 56.50
--- Add Butter + Add Eggs Quantity ---
Products Unit Price Quantity Subtotal
------------------------------------------
Milk 5.50 2 11.00
Bread 8.00 1 8.00
Eggs 12.50 5 62.50
Butter 15.00 2 30.00
------------------------------------------
Total 103.50
Before taxes: 103.50, Tax(8%): 8.28, Total due: 111.78
Unit Price > 10 Items priced at yuan: ["Eggs", "Butter"]
この例では、
push、retain(条件付き削除)、iter_mut().find()(条件付き変更)、およびmap/filter/sum(機能統計)といった、Vecの主要な操作を組み合わせて使用しています。retainは、removeよりも一括削除に適しています。
❓ よくある質問
vec!["a", "b"] と vec!["a"; 2] の違いは何ですか?usize 値、すなわちヒープデータへのポインタ、長さ (len)、および容量 (capacity) が格納されており、合計で 24 バイト(64 ビットシステムの場合)になります。📖 まとめ
Vec<T>は、ヒープ上に動的に割り当てられた配列であり、実行時にサイズを変更することができます- 作成方法:
Vec::new()、vec![]、Vec::with_capacity(n) - 末尾操作:
push追加(O(1)、負荷分散あり)、pop削除(O(1)) - 中間処理:
insert/remove(O(n)、データをシフトする必要がある) - 容量と長さ:
capacityは割り当てられた領域、lenは要素の実際の数、shrink_to_fitは余分なメモリを解放します - Vecと配列は相互に変換可能であり、Vecはコストをかけずにスライスに変換できる
&[T]
📝 練習問題
- 難易度 ⭐: 1 から 10 までの数値を含む
Vec<i32>を作成し、pushを使って 11 と 12 を追加し、次にpopを使って最後の 3 つの要素を削除し、最後に Vec を出力してください。 - 難易度 ⭐⭐: Vec からすべての偶数を削除し、奇数のみを残す関数
fn remove_evens(v: &mut Vec<i32>)を記述してください。mainでvec![1, 2, 3, 4, 5, 6, 7, 8]を使用してテストしてください。結果は[1, 3, 5, 7]になるはずです。 - 難易度 ⭐⭐⭐:ショッピングカートプログラムをシミュレートしてください。
struct Item { name: String, price: f64, quantity: u32 }を定義してください。Vec<Item>としてショッピングカートを作成し、商品の追加、商品名による商品の削除、数量の変更、およびショッピングカートの合計金額の出力を実装してください。mainで、その一連の処理を実演してください。



