Rustで学ぶシステムプログラミング:所有権と借用を完全理解する
Rustの最大の特徴である所有権システムと借用チェッカーを、実践的なコード例を通じて徹底解説します。
Rustで学ぶシステムプログラミング:所有権と借用を完全理解する
Rustは2015年に安定版がリリースされて以来、システムプログラミング言語として急速に普及しています。Stack Overflow Developer Surveyで「最も愛されている言語」を9年連続で獲得するなど、開発者から高い評価を受けています。
Rustが解決する問題
従来のシステムプログラミング言語(C/C++)では、以下の問題が発生しがちでした:
- メモリリーク:確保したメモリを解放し忘れる
- ダングリングポインタ:解放済みメモリへのアクセス
- データ競合:複数スレッドからの同時書き込み
- バッファオーバーフロー:配列境界外へのアクセス
Rustはこれらすべてをコンパイル時に検出します。ガベージコレクタなしで、かつランタイムコストゼロで安全性を実現するのがRustの革新です。
所有権システムの基本
Rustの中心概念は**所有権(Ownership)**です。3つのルールで成り立っています:
- Rustの各値は、所有者(owner)と呼ばれる変数を持つ
- 値の所有者は常に1つ
- 所有者がスコープを抜けると、値は破棄される
fn main() {
let s1 = String::from("hello"); // s1がStringを所有
let s2 = s1; // 所有権がs2にムーブ
// println!("{}", s1); // コンパイルエラー!s1はもう無効
println!("{}", s2); // OK
}
借用(Borrowing)
すべてをムーブしていては不便なので、借用という仕組みがあります。
イミュータブルな借用
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 参照を渡す(所有権はs1のまま)
println!("'{}' の長さは {} です", s1, len);
}
ミュータブルな借用
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
重要なルール:ミュータブルな参照は同時に1つしか存在できません。
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // コンパイルエラー!
println!("{}, {}", r1, r2);
これによりデータ競合をコンパイル時に防ぎます。
ライフタイム
参照が有効な期間をライフタイムと呼びます。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
'aはライフタイムアノテーションで、「返り値の参照は引数と同じかそれより短い期間有効である」ことを表します。
エラーハンドリング
RustはエラーをResult型で扱います。例外機構を持たず、エラーも型システムで管理します。
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
?演算子でエラーを簡潔に伝播できます。
トレイト(Trait)
Rustのトレイトはインターフェースに似た概念ですが、より強力です。
trait Summary {
fn summarize(&self) -> String;
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..50])
}
}
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, self.content)
}
}
並行プログラミング
Rustの所有権システムは並行プログラミングでも力を発揮します。
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("結果: {}", *counter.lock().unwrap()); // 10
}
データ競合はコンパイル時に検出されるため、安全な並行処理が書けます。
Rustのエコシステム
| ツール | 用途 |
|---|---|
cargo |
ビルドシステム・パッケージマネージャ |
rustfmt |
コードフォーマッタ |
clippy |
高度なlinter |
rustdoc |
ドキュメント生成 |
cargo test |
テストランナー |
まとめ
Rustの所有権システムは最初は難しく感じますが、一度理解するとバグの少ない安全なシステムコードを書けるようになります。コンパイラが厳しいのは、実行時のクラッシュを防ぐためです。
WASMやLinuxカーネル、組み込みシステム、CLIツールと幅広い領域で活躍するRustは、次世代のシステムプログラミングの標準となりつつあります。