RustとJavaScriptの構文・メンタルモデル比較
JS/TS開発者のためのRust入門:変数、不変性、関数、エラー処理、構造体vsクラス、およびメモリ管理の相違点。
JavaScriptやTypeScriptからRustへ移行する開発者にとって、構文は波括弧や let、アロー関数(クロージャ)など一見馴染み深く感じられます。しかしその内部には、全く異なる実行モデルとメモリ設計が存在しています。
10歳でもわかる説明:賃貸マンション vs 専用の精密工房#
- JavaScript (フルサービスの賃貸マンション): 入居して電気や水道を自由に使え、ゴミは廊下に置いておけます。定期的に清掃員(ガベージコレクタ)が見回って片付けてくれます。探している物がない時は
undefinedやnullが返されます。 - Rust (専用の精密工作工房): すべての工具に専用のフックと単一の所有者が割り当てられています。借りるのも移動するのも厳密に記録しなければなりません。清掃員はおらず、工具は作業部屋を出た瞬間(コンパイル時確定のRAII破棄)に自動で片付けられます。
graph TD
subgraph JavaScript ["JavaScript 実行時モデル"]
JSEval["動的型 & オブジェクト"] --> JSHeap["V8 ヒープメモリ"]
JSHeap --> GC["バックグラウンドGC(定期的なスレッド停止)"]
JSEval --> JSNull["null / undefined(実行時TypeErrorリスク)"]
end
subgraph Rust ["Rust コンパイル時モデル"]
RustEval["静的型付け & 型推論"] --> RustCheck["借用チェッカー & 所有権検証"]
RustCheck --> Bin["最適化された機械語(Zero-cost, GCなし, RAII破棄)"]
RustEval --> RustEnum["Option<T> & Result<T, E>(コンパイル時強制)"]
end
1. 変数と不変性(Mutability)#
JavaScriptでは const は再代入を防ぐのみで、オブジェクト内部のプロパティは自由に変更できます。一方Rustの let はデフォルトで完全不変(ディープイミュータブル)です。
javascript.js
// JS: let は再代入可能、const は参照の固定のみ
let x = 10;
x = 20; // OK
const user = { name: "Alice" };
user.name = "Bob"; // OK! オブジェクト内部は変更可能javascriptrust.rs
// Rust: デフォルトで不変、変更には 'mut' が必要
let x = 10;
// x = 20; // ❌ エラー: 不変変数への再代入不可
let mut y = 10;
y = 20; // ✅ OK
struct User { name: String }
let user = User { name: String::from("Alice") };
// user.name = String::from("Bob"); // ❌ エラー: user が mut でないため変更不可rust2. 関数と暗黙の戻り値(Implicit Returns)#
Rustではブロック末尾にセミコロン ; を付けない最後の式が自動的に戻り値となります。
javascript.js
// JavaScript
function add(a, b) {
return a + b;
}
const multiply = (a, b) => a * b; // アロー関数の暗黙リターンjavascriptrust.rs
// Rust: 型注釈が必須。末尾に ';' を付けない式が戻り値になる
fn add(a: i32, b: i32) -> i32 {
a + b // セミコロンなし -> 自動的に return
}
// クロージャ(無名関数)
let multiply = |a: i32, b: i32| a * b;rust3. オブジェクト・クラス vs 構造体・トレイト#
JavaScriptは this を伴うプロトタイプ/クラスベースのOOPです。Rustはデータ(struct)と振る舞い(impl)、インターフェース(trait)を明確に分離します。
javascript.js
// JavaScript Class
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.area());javascriptrust.rs
// Rust: 構造体(データ) + Implブロック(振る舞い)
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 関連関数(コンストラクタの慣例)
fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
// &self を受け取るメソッド
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(10, 20);
println!("面積: {}", rect.area());
}rust4. null / undefined vs Option<T>#
JavaScript開発者が最も遭遇する実行時エラーは TypeError: Cannot read properties of undefined です。Rustは Option<T> により、null の概念をコンパイル段階で完全に排除します。
graph LR
JSNullCheck["JS: user?.address?.city"] -->|実行時にundefinedに遭遇| Crash["実行時TypeError(チェック漏れ時)"]
RustOption["Rust: Option<String>"] -->|コンパイラがmatchを強制| SafeHandling["コンパイル時に100%安全を保証"]
javascript.js
// JS: null または undefined を返す可能性
function findUser(id) {
if (id === 1) return { name: "Alice" };
return null;
}
const user = findUser(2);
// console.log(user.name); // ❌ 実行時エラー: Cannot read properties of nulljavascriptrust.rs
// Rust: Option<User> を返すことを強制
struct User { name: String }
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Alice") })
} else {
None
}
}
fn main() {
let user = find_user(2);
// コンパイラが None の処理を強制する
match user {
Some(u) => println!("ユーザー名: {}", u.name),
None => println!("見つかりませんでした"),
}
}rust5. エラー処理: try/catch vs Result<T, E>#
JavaScriptは例外のスロー(throw)によってスタックを飛び越えます。Rustはエラーを通常の戻り値 Result<T, E> として扱います。
javascript.js
// JS: try / catch / throw
try {
const data = JSON.parse(rawText);
console.log(data);
} catch (err) {
console.error("JSON解析失敗:", err.message);
}javascriptrust.rs
// Rust: Result<T, E> と ? 演算子
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
let num = s.trim().parse::<i32>()?;
Ok(num * 2)
}
fn main() {
match parse_number("42") {
Ok(val) => println!("計算結果: {val}"),
Err(e) => eprintln!("パース失敗: {e}"),
}
}rust比較クイックリファレンス#
| 比較項目 | JavaScript / TypeScript | Rust |
|---|---|---|
| 型システム | 動的型 (JS) / 構造的型 (TS) | 静的型付け、公称型 (Nominal) |
| メモリ管理 | ガベージコレクタ (V8) | 所有権 (Ownership) & RAII |
| 欠損値 | null および undefined | Option<T> (Some または None) |
| エラー処理 | try / catch / throw | Result<T, E> と ? 演算子 |
| 非同期処理 | 単一スレッドイベントループ | マルチスレッド非同期ランタイム (Tokio) |
| パッケージ管理 | npm / bun (package.json) | cargo (Cargo.toml) |
| 不変性 | const(浅い参照固定のみ) | let(デフォルトで完全不変) |
まとめ#
- Rustの構文はJS/TS開発者にとって直感的ですが、コンパイル時の検証ルールが厳格です。
- 変数はデフォルトで不変であり、変更には明示的な
mutが必要です。 - クラス継承は存在せず、
structとtraitの組み合わせで設計します。 OptionとResultがnullや例外を置き換え、実行時クラッシュを未然に防ぎます。