rustを学んで③
Rustの学習記録:躓いたポイントと疑問解決まとめ③
ズブの素人の私が、Rustを学び始めて最初に躓いたポイントと、その解決方法をまとめました。同じような疑問を持つ方の参考になれば幸いです。
1. ベクタ(Vec)と配列の違い
最初の疑問:「vecって要するにJSの配列みたいなもの?」
Rustを学び始めて最初に出会ったVec型。「可変長でヒープに格納される」と聞いて、「これってJavaScriptの普通の配列と同じでは?」と思いました。
結論:似ているが、重要な違いがある
共通点
// Rust
let mut v = vec![1, 2, 3];
v.push(4);
v.pop();
let len = v.len();
// JavaScript
let v = [1, 2, 3];
v.push(4);
v.pop();
let len = v.length;
どちらも動的にサイズを変更でき、要素の追加・削除が可能です。
決定的な違い:型の制約
// Rust: 同じ型しか入らない
let v: Vec<i32> = vec![1, 2, 3];
// v.push("文字列"); // コンパイルエラー!
// JavaScript: 何でも混在できる
let v = [1, 2, "文字列", true]; // OK
学び:RustのVecは「型が統一されたJavaScriptの配列」と理解すると良い
2. 範囲外アクセスで実行時パニック
躓いたコード
let mut vect = vec![10, 20, 30];
vect.push(40);
println!("{} {} {} {}", vect[0], vect[1], vect[2], vect[4]); // パニック!
エラーメッセージ:
thread 'main' panicked at src/main.rs:4:60:
index out of bounds: the len is 4 but the index is 4
疑問:「なぜコンパイル時にエラーにならないの?」
JavaScriptでも実行時エラーになるのは分かっていましたが、Rustは静的型付け言語なのに、なぜコンパイル時に検出できないのか疑問でした。
答え:Vecのサイズは実行時に決まるから
Vecは可変長なので、pushやpopで動的にサイズが変わる- コンパイラは実行時の値を予測できない
- だから実行時までエラーを検出できない
固定長配列ならコンパイルエラーになる
let arr = [10, 20, 30, 40];
println!("{}", arr[4]); // コンパイルエラー!
配列はサイズが固定なので、コンパイラが範囲外アクセスを検出できます。
学び:柔軟性(サイズ変更可能)のトレードオフとして、範囲外アクセスは実行時まで検出できない
安全なアクセス方法
// 危険:パニックの可能性
let value = vect[4];
// 安全:Optionで返ってくる
match vect.get(4) {
Some(value) => println!("値: {}", value),
None => println!("その要素は存在しません"),
}
3. 型を混在させたい時の方法
疑問:「Rustで異なる型を1つのVecに入れたい時はどうするの?」
JavaScriptなら[1, "hello", true]のように何でも入れられますが、Rustでは同じ型しか入りません。
解決策:Enum(列挙型)を使う
enum Value {
Int(i32),
Float(f64),
Text(String),
}
let mut v = vec![
Value::Int(42),
Value::Text(String::from("hello")),
Value::Float(3.14),
];
// 使うときはパターンマッチング
for item in &v {
match item {
Value::Int(n) => println!("整数: {}", n),
Value::Float(f) => println!("浮動小数点: {}", f),
Value::Text(s) => println!("文字列: {}", s),
}
}
学び:Enumは型安全を保ちつつ、異なる種類のデータを扱える最も推奨される方法
4. スライスは構文、メソッドではない
疑問:「&s[0..5]ってメソッドじゃないの?」
JavaScriptではs.slice(0, 5)のようにメソッドを使うので、Rustでも同じだと思っていました。
答え:スライスは言語組み込みの構文
let s = String::from("hello world");
let slice = &s[0..5]; // これは構文(メソッド呼び出しではない)
なぜ構文なのか?
配列やVecでも同じ書き方ができるようにするため:
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // [2, 3]
let v = vec![10, 20, 30, 40];
let slice = &v[0..2]; // [10, 20]
統一されたインターフェースなので、言語レベルでサポートされています。
でも、メソッドもたくさんある
let s = String::from("hello world");
s.split(' ') // メソッド
s.split_whitespace() // メソッド
s.len() // メソッド
ドット(.)で呼び出すものはメソッドです。
学び:スライス[..]だけが特別な構文で、その他は普通にメソッドがある
5. insertの使い分け
疑問:「insertってHashMapのものじゃないの?」
HashMapでinsertを見た後、Vecでもinsertが出てきて混乱しました。
答え:同じ名前でも、型によって意味が違う
// Vecのinsert
let mut v = vec![1, 2, 3];
v.insert(1, 99); // インデックス1に99を挿入
// [1, 99, 2, 3]
// HashMapのinsert
let mut map = HashMap::new();
map.insert("key", 100); // キーと値を挿入
- Vec:
insert(インデックス, 値) - HashMap:
insert(キー, 値)
学び:静的型付け言語では、型によってメソッドの動作が決まる。JavaScriptでは名前が違うが、Rustは「挿入する」という共通概念で同じ名前を使っている
6. HashMapは配列ではない
疑問:「HashMapって配列になるの?」
キーと値のペアを保存すると聞いて、配列の一種かと思いました。
答え:HashMapと配列は別物
配列/Vec
- インデックス(数値)でアクセス:
v[0],v[1] - 順序が保証される
- 連続したメモリ
HashMap
- キー(任意の型)でアクセス:
map["Blue"] - 順序は保証されない
- ハッシュテーブルで実装
let mut map = HashMap::new();
map.insert("apple", 100);
map.insert("banana", 200);
map.insert("cherry", 300);
// 出力順序は不定!
for (k, v) in &map {
println!("{}: {}", k, v); // apple, banana, cherryの順とは限らない
}
学び:HashMapはJavaScriptのMapやObjectに近い。順序が必要なら別のデータ構造を使う
7. 構造体と所有権
疑問:「なぜbuild_user関数でキーに所有権を渡すの?」
scores.insert(String::from("Blue"), 10);
答え:HashMapのinsertは所有権を奪う設計だから
let key = String::from("Blue");
scores.insert(key, 10);
// この後、keyは使えない!(所有権が移動した)
なぜこの設計?
- キーが外部で変更されてハッシュ値が変わるのを防ぐ
- メモリ安全性を保証
より実用的なパターン
// パターン1:引数をStringにする
fn build_user(email: String, username: String) -> User {
User { email, username, /* ... */ }
}
build_user("email".to_string(), "username".to_string())
// パターン2:引数を&strにする(より一般的)
fn build_user(email: &str, username: &str) -> User {
User {
email: email.to_string(),
username: username.to_string(),
/* ... */
}
}
build_user("email", "username") // 呼び出し側が楽
学び:パターン2の方が実用的。呼び出し側がシンプルで、柔軟性も高い
8. 構造体更新構文の間違い
躓いたコード
let user2 = User {
username: String::from("uni"),
email: String::from("email2"),
.user1 // エラー!
};
エラー:
expected identifier, found `.`
正しい書き方:
let user2 = User {
username: String::from("uni"),
email: String::from("email2"),
..user1 // ドットが2つ!
};
..user1は「残りのフィールドはuser1から取る」という意味です。
学び:構造体更新構文は..(ドット2つ)。1つだと構文エラーになる
9. Debugトレイトでの表示
躓いたコード
#[derive(Degug)] // タイポ
struct User { /* ... */ }
println!("{}", user1); // エラー
エラー:
`User` doesn't implement `std::fmt::Display`
正しい書き方:
#[derive(Debug)] // Degug → Debug
struct User { /* ... */ }
println!("{:?}", user1); // {:?} を使う
{}:Displayトレイトが必要(ユーザー向けの表示){:?}:Debugトレイトで表示(デバッグ用){:#?}: 整形されたDebug表示
学び:自作の構造体を表示するには#[derive(Debug)]を付けて、{:?}で表示する
10. データ構造の使い分け
疑問:「配列、タプル、HashMap...いつ何を使えばいいの?」
JavaScriptやPythonでも似たようなデータ構造があり、しっかり理解できていない気がしていました。
基本的な分類:
1. 順序付きコレクション(インデックスでアクセス)
- Vec/配列:
[1, 2, 3] - 用途:順序が重要、番号で管理
2. キーと値のペア(キーでアクセス)
- HashMap:
{"name": "太郎", "age": 20} - 用途:名前で管理したい
3. 固定の複数の値(位置で意味が決まる)
- タプル:
(名前, 年齢, 住所) - 用途:関連する異なる型のデータをまとめる
使い分けの目安:
リスト of 同じ種類のもの → Vec/配列
例:スコアのリスト、ユーザーのリスト
名前で引きたい → HashMap
例:設定値、ユーザー情報の検索
2〜3個の関連データ → タプル
例:座標(x, y)、関数の複数の戻り値
学び:これは言語を超えた共通概念。Rustだけでなく、他の言語でも同じ考え方が使える
11. 構文とメソッドの違い
疑問:「そもそも構文って何?」
スライスの話で「構文」という言葉が出てきて、メソッドとの違いが分からなくなりました。
構文(シンタックス)とは: プログラミング言語のルールや書き方そのもの。言語が最初から提供している「文法」。
// 構文の例
if x > 10 { } // if文
for i in 0..10 { } // for文
let x = 5; // 変数宣言
&s[0..5] // スライス
fn main() { } // 関数定義
メソッドとは: 型に紐付いた関数。既存の型に対して呼び出せる機能。
// メソッドの例
s.len()
s.split(' ')
v.push(10)
違いのまとめ:
| 構文 | メソッド | |
|---|---|---|
| 誰が提供? | 言語自体 | 型(標準ライブラリや自作) |
| 自分で作れる? | ❌ 不可 | ✅ 可能 |
| 例 | if, for, let, [..] | .len(), .push(), .split() |
学び:基本ルールが構文、便利機能がメソッド。JavaScriptでも同じ区別がある
まとめ
Rustを学び始めて最初に躓いたポイントをまとめました。特に印象的だったのは:
- 型の制約が厳しいが、安全性のため:JavaScriptのような自由度はないが、コンパイル時に多くのエラーを防げる
- 所有権システムは独特だが理にかなっている:最初は戸惑うが、メモリ安全性を保証するための仕組み
- 静的型付け言語の特性を理解すること:型によってメソッドの動作が決まる、実行時ではなくコンパイル時に多くのチェックが入る
- 構文とメソッドの区別:言語の基本機能と、型に付属する機能を区別して理解する
今後の学習方針:
- ルールが複雑なので、まずはシンプルな書き方から始める
- とほほのRust入門や公式Docで基礎を固める
- その後rustlingsで演習に取り組む
ADHDっぽく色々なところに興味が飛びますが、それがプログラミング学習には良い方向に働いている気がします。(前向きですね… 疑問が出たらすぐに調べ、手を動かして確認することで、着実に理解が深まっていると感じています。
この記事が、同じようにRustを学び始めた方の参考になれば幸いです。間違いや改善点があれば、コメントで教えていただけると嬉しいです!