Rustで学ぶシステムプログラミング:所有権と借用を完全理解する

Rustの最大の特徴である所有権システムと借用チェッカーを、実践的なコード例を通じて徹底解説します。

rust
systems
memory
programming

Rustで学ぶシステムプログラミング:所有権と借用を完全理解する

Rustは2015年に安定版がリリースされて以来、システムプログラミング言語として急速に普及しています。Stack Overflow Developer Surveyで「最も愛されている言語」を9年連続で獲得するなど、開発者から高い評価を受けています。

Rustが解決する問題

従来のシステムプログラミング言語(C/C++)では、以下の問題が発生しがちでした:

  • メモリリーク:確保したメモリを解放し忘れる
  • ダングリングポインタ:解放済みメモリへのアクセス
  • データ競合:複数スレッドからの同時書き込み
  • バッファオーバーフロー:配列境界外へのアクセス

Rustはこれらすべてをコンパイル時に検出します。ガベージコレクタなしで、かつランタイムコストゼロで安全性を実現するのがRustの革新です。

所有権システムの基本

Rustの中心概念は**所有権(Ownership)**です。3つのルールで成り立っています:

  1. Rustの各値は、所有者(owner)と呼ばれる変数を持つ
  2. 値の所有者は常に1つ
  3. 所有者がスコープを抜けると、値は破棄される
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は、次世代のシステムプログラミングの標準となりつつあります。