📖
Rustを学ぶと世界が変わる
はじめに
GLOBISでエンジニアをしているkazukiと申します。
Rustを多くの人に学んで欲しいと感じこの記事を執筆しました。
私自身、仕事でRustを使うことはありませんがRustの学習を通して、
エンジニアとしての視点や感覚がよくなる体験を得ております。
但し、Rust自身は最初が難しくなかなか取っ付きにくい言語でもあります。
入門としてこの記事が役に立ってくれれば幸いです。
なんでRust?
- 他のモダン言語と比較して仕事で見ないRust
- それでもRustはエンジニアの思考能力を鍛える教材としても価値がある
- 静的型付けや低レイヤ未経験の人でも読めるよう、できる限り噛み砕いて解説します
そもそもRustって何?
- Rust は Mozilla が開発した、安全性と高速性を両立するプログラミング言語です
- C/C++並みのパフォーマンスを持ちながら、メモリ安全性をコンパイル時に保証します
- その安全性を支える仕組みとして所有権・借用・ライフタイムがあります
Rustの仕組み
まずここでは所有権・借用・ライフタイムについて簡単に説明します。
所有権について
- メモリ上のデータに対して「誰が片付ける責任を持つか」を決める仕組み
- 所有権には3つのルールがある
1. すべてのデータには持ち主がいる
let motinushi = String::from("data");
2. 持ち主は1人だけ
let old_motinushi = String::from("data");
let new_motinushi = old_motinushi;
println!("{}", old_motinushi);
3. 持ち主がいなくなるとデータは自動で破棄される(メモリ解放)
fn main() {
let old_motinushi = String::from("data");
{
let new_motinushi = old_motinushi;
println!("{}", new_motinushi);
} // ここでnew_motinushiはdrop(解放)される。
println!("{}", old_motinushi); // old_motinushiはmoveしてしまっているので使えない
println!("{}", new_motinushi); // スコープ外で new_motinushi が見えない
}
他言語の違い
- 多くの言語では同じ見た目の代入が「参照のコピー」だったり「コピー」が走ったりして、元の変数がそのまま使えることが多いです。
- Rustはここをmoveとdropで厳密に扱うため、
old_motinushiの再利用をコンパイル時に禁止します。
JavaScriptを例にしたGC言語との比較
※GC(ガベージコレクション)は比較はするが深入りしません。
function main() {
let oldOwner = { text: "data" };
{
let newOwner = oldOwner; // 参照のコピー(同じ実体を指す)
console.log(newOwner.text);
}
console.log(oldOwner.text); // 普通に使える。
}
- GC言語では代入は「所有権の移動」ではなく、単なる参照のコピーである。
そのため、スコープを跨いでも元の変数が使えなくなることはない。 - GCにはメモリ管理をしなくていいメリットがあるがデメリットもたくさんある。
- RustはGCなしでメモリ安全性と速度を両方持ち合わせた言語である。所有権とその他の仕組みはその性質を持ち合わせるためのものである。
借用について
- 「所有権を渡さずにデータを使わせてもらう仕組み」
- 多くの場合は所有権を移動させず、データへのアクセス権だけを一時的に借りれば十分
- 無闇に所有権を渡すと、意図しない解放でコンパイルエラーを生むことがある
- 借用には2つの種類と3つのルールがあります。
借用の種類
1. 不変借用
- 参照先を変更しないが利用する必要がある場合に使う。
fn print_suruyo(syakuyou: &String) {
println!("{}", syakuyou);
}
fn main() {
let motinushi = String::from("data");
print_suruyo(&motinushi);
println!("{}", motinushi);
}
2. 可変借用
- 参照先を変更する場合に使う。
fn add_bikkuri(syakuyou: &mut String) {
syakuyou.push('!');
}
fn main() {
let mut motinushi = String::from("data");
add_bikkuri(&mut motinushi);
println!("{}", motinushi);
}
借用のルール
借用が安全に機能するために、Rust は3つの制約を課しています。
1. 1つのデータに対して不変な借用(&)は何個でも作成できる
let motinushi = String::from("a");
let syakuyou1 = &motinushi;
let syakuyou2 = &motinushi;
2. 1つのデータに対して可変な借用は1つだけ
let mut motinushi = String::from("a");
let syakuyou1 = &mut motinushi;
let syakuyou2 = &mut motinushi; // ❌ エラー
3. 1つのデータに対して不変と可変は同時に持てない
let mut s = String::from("a");
let syakuyou1 = &s; // 不変借用
let syakuyou2 = &mut s; // 可変借用 ❌ 同時不可
借用を使わないと何が起きるか?
fn print_suruyo(syakuyou: String) {
println!("{}", syakuyou);
} // syakuyouはここで解放される
fn main() {
let motinushi = String::from("data");
print_suruyo(motinushi);
println!("{}", motinushi); // ❌ 所有権が移動したのでエラー
}
ライフタイムについて
- 「所有権と借用のルールが破られていないかをコンパイル時に検証する仕組み」です。
- コンパイラは参照の「生存期間(いつからいつまで有効か)」を推論して確認しています。
- ここまで所有権と借用を理解してきた読者はライフタイムの本質をすでに理解しています。
なぜライフタイムが必要なのか?
補足:メモリ問題とは
-
二重解放
同じメモリを2回解放してしまい、プログラムが不正な状態になること -
ダングリングポインタ
すでに解放されたメモリを参照してしまうこと(=死んだデータを指す参照) -
メモリ破壊
いったん解放されたメモリが別の用途で再利用されたあと、
古い参照がそこを書き換えてしまい、全く無関係なデータを壊すこと
仕組みに関するまとめ
- 所有権と借用で"書き方のルール"を決め、ライフタイムで"そのルールが破られていないか"をコンパイル時にチェックする。
- これが仕組みが組み合わさることで、Rust は高いメモリ安全性を実現しています。
補足:なぜメモリ安全なのか
-
所有権
- 誰が解放するかが常に1人に決まっているので二重解放が起こらない。
-
借用
- 借用ルールにより、危険な同時アクセスが禁止される。メモリ破壊につながるような競合状態のコードはコンパイルエラーになる。
-
ライフタイム
- 参照が参照先より長生きするコードはコンパイル時に弾かれる。
- ダングリングポインタや解放済み領域へのアクセスがそもそも書けない。
- これら3つが すべてコンパイル時にチェックされる ため、
C/C++のようなメモリ管理バグが「実行前の段階で」多くが防がれる。
Rustを学ぶとなぜ世界が変わるか?
Rustを学ぶとコードの見え方が根本から変わる。
その変化の正体は、Rustが強制してくる 設計の原理にある。
1. 不変性(immutability)を起点として考えるようになる
2. 副作用を意識せざるを得ず、明示性の高い設計に矯正される
実務ではどう活きるのか
言語に依存しない"抽象的な設計力"が飛躍的に高まります
- 「どこで状態が変わるのか?」
- 「どこに責務があるのか?」
- 「この参照は本当に安全か?」
Rustを通じて、上記のような視点が自然に身につきます。
不変性・明示性・安全性に触れることで判断力が鍛えられる
- 設計判断の質が上がる
- 曖昧な設計に敏感になる
- バグを未然に防ぐ発想が育つ
といったエンジニアとしての根本的な判断力が鍛えられます。
次にやれば良さそうなこと
- 所有権・借用・ライフタイムの癖に慣れる(小さなコードを書く)
-
String/&strmove/copy/cloneを理解する - 小さなツール作成 or 競技プログラミングで書き慣れる
- スマートポインタを理解する。
- 所有権が必要なパターンを理解する。
Discussion