所有権と借用:Rust独自のメモリ管理を理解する

Rustを学ぶ上で避けて通れないのが「所有権(Ownership)」と「借用(Borrowing)」という概念です。Rustのメモリ管理は他の言語と比べてユニークであり、これらの仕組みを理解することで、Rustが提供する高いメモリ安全性を体感できるようになります。

この記事では、Rustの所有権と借用の基本的なルールについて、実際のコード例を用いながら解説します。

本1

RustによるWebアプリケーション開発

Rustによるアプリケーション開発のベストプラクティス!
Rustを現場で使うときがきた!

Amazonで購入
本2

手を動かして考えればよくわかる 高効率言語 Rust

豊富なサンプルはすべて書き下ろし!
PythonからRustへの道がここにある!
多くの題材でPythonとRustのコードを併記し、違いがわかるようにしてあります。

Amazonで購入
本3

Rustで学ぶWebAssembly

WebAssemblyとRustの基本についておさらいしたあと、
WebAssemblyを使った簡単な機能の実装、アプリのWASM化、コンテナ環境へのデプロイと難易度を上げながら、上記メリットが実感できるようなハンズオンを用意。

Amazonで購入

所有権とは?

Rustでは、すべての値は必ず「所有者(owner)」を持ちます。

所有者は1つしか存在せず、ある変数が値を所有している場合、他の変数にその所有権を譲渡したり、借用したりしなければなりません。

この所有権システムにより、Rustはメモリの解放を自動的に管理します。

所有権のルール

  1. 各値には所有者が一つ存在する。
  2. 所有者がスコープを抜けると、値はドロップされメモリが解放される。
  3. 所有権は移動することができる。

以下のコードで、所有権の基本的な動きを確認してみましょう。

fn main() {
    let s1 = String::from("こんにちは"); // s1がString型の値を所有
    let s2 = s1; // 所有権がs1からs2に移動(ムーブ)
    
    // println!("{}", s1); // s1の所有権はすでに移動しているため、ここでエラー
    println!("{}", s2); // s2は正しく所有しているので表示可能
}

ポイント:

  • let s2 = s1 の行で、s1からs2に所有権がムーブ(移動)されます。所有権が移動した後、s1は無効になり、もう使用できません。
  • println!マクロでs1を使おうとするとコンパイルエラーが発生します。

所有権のムーブとコピー

基本的に、所有権はムーブされますが、固定サイズのデータ型(整数や浮動小数点など)はムーブではなく「コピー」されます。

fn main() {
    let x = 5; // xはi32型の整数(固定サイズの型)
    let y = x; // xの値がコピーされ、yも所有
    println!("x: {}, y: {}", x, y); // xとy両方が使用可能
}

ポイント:

  • i32のような固定サイズの型は、コピーされるため、所有権が移動するわけではありません。
  • xyも有効な状態で使用できます。

借用とは?

所有権が移動せずに、変数を使い回す方法が「借用(Borrowing)」です。

Rustでは、変数を借用することで、所有権を保ったまま他の部分でその値を参照することができます。

参照と借用

Rustでは、&を使って参照を借用できます。

借用には「不変借用」と「可変借用」の2種類があります。

fn main() {
    let s1 = String::from("Rust");
    let len = calculate_length(&s1); // s1を借用(不変借用)
    println!("'{}'の長さは{}です。", s1, len); // s1は引き続き有効
}

fn calculate_length(s: &String) -> usize {
    s.len() // sの長さを返す
}

ポイント:

  • &s1s1を借用していますが、所有権は移動していません。
  • 参照(借用)された値は読み取り専用となります。

可変借用

一度に1つだけ、可変な借用が可能です。

可変借用をするには、&mutを使用します。

fn main() {
    let mut s = String::from("Hello"); // mutで可変なStringを宣言
    change(&mut s); // 可変借用
    println!("{}", s); // 変更後のsを表示
}

fn change(s: &mut String) {
    s.push_str(", world!"); // 文字列に追加
}

ポイント:

  • 可変借用は、同じ変数に対して同時に複数存在することはできません(競合を防ぐため)。
  • &mut s で可変な借用を行い、関数内でpush_strメソッドで値を変更します。

借用規則

Rustでは、安全なメモリ管理を実現するためにいくつかのルールがあります。

不変借用と可変借用のルール

  1. 不変借用は何回でも可能です。
  2. 可変借用は同時に1つだけです。
  3. 不変借用と可変借用は同時には許されません。
fn main() {
    let mut s = String::from("Rust");
    
    let r1 = &s; // 不変借用
    let r2 = &s; // 不変借用(何回でもOK)
    // let r3 = &mut s; // 可変借用を同時に行うとエラー

    println!("{}と{}", r1, r2); // 不変借用の値は安全に使用可能
}

ポイント:

  • 不変借用は複数同時に行うことができますが、可変借用と同時に行うとエラーになります。

スライス

スライスは、コレクション全体ではなく、その一部を借用したい場合に使います。

fn main() {
    let s = String::from("hello world");
    let hello = &s[0..5]; // スライスを作成
    let world = &s[6..11];
    println!("{} {}", hello, world);
}

ポイント:

  • スライスは不変借用であり、スライス中の要素を変更することはできません。

実践例

借用と所有権を使った文字列の操作

fn main() {
    let mut s = String::from("Rust Programming");

    // 文字列を分割して取得する関数を呼び出す
    let word = first_word(&s);
    println!("最初の単語: {}", word);

    s.clear(); // 可変借用で文字列を空にする
    // println!("最初の単語: {}", word); // この部分はエラーになる
}

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

ポイント:

  • 借用とスライスを組み合わせて、効率的に文字列の一部を扱うことができます。
  • s.clear()で文字列を変更した場合、以前のスライス参照は無効になります。

練習問題

練習問題1: 所有権の移動

以下のコードを修正し、所有権が適切に管理されるようにしてください。

fn main() {
    let s1 = String::from("Rust");
    let s2 = s1;
    println!("{}", s1); // この行でエラーが発生する
}

練習問題2: 借用を使った文字列操作

文字列を入力し、その文字列が含む単語の数を返す関数を作成してください。関数は借用を使用し、所有権を移動しないようにしてください。


まとめ

今回の記事では、Rustの所有権と借用に関する基本的な概念を学びました。

これらのメモリ管理機構により、Rustは高い安全性を持ちながら効率的に動作する言語となっています。

所有権の移動や借用のルールに慣れることで、より深いRustプログラミングの理解を深めることができるでしょう。


次回予告のデザイン

次回予告

次回は「Rustのライフタイムとその活用法」をテーマに、
所有権と借用をさらに深め、ライフタイムについて学びます。

Author