本文へスキップ
BecomeCoder

Rustコース · 第4章 所有権 · レッスン18

関数と所有権 ― 引数に渡すとムーブする

ブラウザで完結

導入

所有権のルールは、変数間の代入だけでなく関数呼び出しでもまったく同じように働きます。String を関数に渡すと、所有権が関数の中へムーブします。ここを理解すると、なぜ次章の「借用」が必要になるのかが腑に落ちます。

説明

String を関数に渡してみます。

fn main() {
    let s = String::from("hello");

    take_ownership(s);        // s の所有権が関数へムーブする

    // println!("{s}");       // ← エラー! s はもう main のものではない
}

fn take_ownership(text: String) {
    println!("受け取った: {text}");
}   // ← ここで text がスコープを抜け、ヒープが解放される
  • take_ownership(s) で、s の所有権は引数 textムーブします。mains は無効になります。
  • 関数が終わると textスコープを抜け、ヒープが解放されます。だから main に戻ってきたとき、s はもう指す先がなく、使えません。

渡した値を呼び出し側でも使い続けたいなら、所有権を返してもらう必要があります。

fn main() {
    let s = String::from("hello");
    let s = give_back(s);        // 所有権を渡し、戻り値で受け取り直す
    println!("戻ってきた: {s}"); // OK
}

fn give_back(text: String) -> String {
    println!("処理中: {text}");
    text                          // 所有権を呼び出し側へ返す
}

「渡す → 返してもらう」で使い続けられますが、毎回これを書くのは明らかに面倒です。値を使わせてもらうだけ(所有権は渡さない)で済ませたい——この自然な要求に応えるのが、次章の 参照と借用 です。関数に &s と「参照」を渡せば、所有権はムーブせず、呼び出し側が持ったまま関数に中身を見せられます。

graph TD
    a["main: s が所有"] -->|"take_ownership(s)"| b["関数へ所有権がムーブ"]
    b --> c["関数終了で drop<br/>main の s は無効"]
    a2["main: s が所有"] -->|"borrow(&s)"| d["関数は借りるだけ<br/>所有権は main のまま"]
    d --> e["関数終了。s はまだ使える"]

試すには

上の1つ目のコードで take_ownership(s); のあとに println!("{s}") を足すとエラーになります。これを「毎回返り値で返す」以外の方法で解決するのが借用です。次章で take_ownership(&s) に変えるだけで、s を使い続けられることを確認します。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。