「TAURI」を使う前に、まず「Rust」の所有権を理解する

2023年11月2日(木)
大西 武 (オオニシ タケシ)
第4回の今回は、「TAURI」をプログラミングしていく前に「Rust」で最も重要な「変数」に与えられる「所有権」について解説します。

所有権の「参照」「借用」について

所有権のクローンは別のアドレスに複製を作りましたが、元の変数と同じアドレスを持ちながら元の変数から別の変数や引数に値を借りることもできます。そのためには、変数や引数を宣言する際に、元の変数のアドレスを「参照」する「&」を付けることで値を借りることができます。関数の引数に参照を取ることを「借用」と言います。

例えば、次のサンプルコードのように「good1」変数の前で「&」を付けて参照すれば、代入した後でも「good1」変数に所有権が残るため「main()」関数のスコープ内で「good1」変数が使えます。

・所有権の参照のサンプルコード
fn main() {
    let good1 = String::from("Good");
    let _good2 = &good1;
    println!("{}",good1); // エラーなし
}

【サンプルコードの解説】
「good1」変数に"Good"という文字列を代入して宣言します。
「_good2」変数に「good1」変数を参照したアドレスを代入して宣言します。
「good1」変数の文字列をターミナルに表示します。

所有権の「参照」「借用」「関数」について

関数の引数で借用するには、変数の前で参照することを意味する「&」を付け、関数宣言時に「引数の型」の前でも参照することを意味する「&」を付けます。

図7とサンプルコードのように借用することにより、「good_str」関数内のスコープで引数が所有権を持たないため所有権がこのスコープで解放されることはなく、元の「good1」変数にはまだ所有権が残ります。

図7:所有権の「参照」「借用」「関数」

・所有権の参照・借用・関数のサンプルコード
fn main() {
    let good1 = String::from("Good");
    good_str(&good1);
    println!("{}",good1); // エラーなし
}

fn good_str(good: &String) {
    println!("{}",good); // エラーなし
}

【サンプルコードの解説】
「good1」変数に"Good"という文字列を代入して宣言します。
「good_str」関数に「&good1」という風に変数の前に「&」を付け、関数の引数の「good: &String」という風に型の前に「&」を付けて参照できます。
「good」引数の文字列をターミナルに表示します。
「good1」変数の文字列をターミナルに表示します。

所有権の「参照」「借用」「関数」「ミュータブル」について

関数の引数で借用して関数内で値を変更する場合には、図8とサンプルコードのように変数の前で参照する「&mut」を付け、引数の型の前で参照する「&mut」を付けます。

また、ミュータブルな変数をイミュータブルに参照した後にミュータブルに変数を参照したり、ミュータブルな参照が複数あるとエラーになります。

図8:所有権の「参照」「借用」「関数」「ミュータブル」

・所有権の参照・借用・関数・ミュータブルのサンプルコード
fn main() {
  let mut good1 = String::from("Good");
  good_str(&mut good1);
  println!("{}",good1); // エラーなし
}

fn good_str(good: &mut String) {
  good.push('y');
}

【サンプルコードの解説】
「good1」変数に"Good"という文字列を代入してミュータブル(mut)で宣言します。
「good_str」関数に引数としてミュータブルな「good1」変数を参照して渡します。
「good」引数の後ろに"y"文字を足して"Goody"という文字列に変更します。
「good_str」関数内で変更された「good1」変数の文字列"Goody"をターミナルに表示します。

for文におけるイテレータについて

「for文」とは第3回でも解説した通り、決まった回数だけループする繰り返し処理の「制御構文」のことです。そのfor文で値を借用するには「iter()」メソッドで「イテレータ」を使います。そうすれば「v1」vec!マクロの各要素を参照するだけで、所有権はムーブされません。

次のサンプルコードのように、for文でコレクションは所有権を所有せず一時的に借りるだけで、ループ後もコレクションを再利用できます。「vec!」マクロに「iter()」関数を付けけないと所有権がfor文のスコープを出るときに解放されてしまい、ループ後に再利用できません。

・for文とイテレータのサンプルコード
fn main() {
    let v1 = vec!["A","B","C"];
    for v2 in v1.iter() {
        println!("{}",v2);
    }
    println!("{:?}",v1); // エラーなし
}

【サンプルコードの解説】
vec!マクロの「v1」配列に"A"と"B"と"C"の文字列を代入して宣言します。
「for v2 in v1.iter()」で「v2」変数に"A"と"B"と"C"の値が順番に代入され「{}」内をループ処理します。
「iter(イテレータ)」はループごとにコレクションの要素を借用します。
「v1」配列の文字列をターミナルに表示します。

スライス型について

所有権のない別のデータ型に「スライス型」があります。文字列や配列やタプルなどの複数の要素を持つ値の中の一部、または全部を参照することもできます。例えば"Good"文字列の0~3番目未満の"Goo"文字列だけ取得する感じです。

図9とサンプルコードのように文字列変数の前に「&」を付け、変数の後に「[」と「]」で囲って要素の何番目から何番目未満までの要素か「..」で範囲を指定します。

図9:「スライス型」

・スライス型のサンプルコード
fn main() {
    let good1 = String::from("Good");
    let good2 = &good1[0..3];
    println!("{}",good1); // エラーなし
    println!("{}",good2); // エラーなし
}

【サンプルコードの解説】
「good1」変数に"Good"という文字列を代入して宣言します。
「good2」変数に"Good"という文字列の0~3未満の文字列"Goo"を代入して宣言します。
「good1」変数の文字列をターミナルに表示します。
「good2」変数の文字列をターミナルに表示します。

おわりに

今回は変数に所有権を与え、所有権の「ムーブ」「クローン」「コピー」「参照」「借用」の5大要素と、「所有権と関数」「イテレータでの借用」「スライス型」について解説しました。

たとえ詳細に所有権を解説しても、Rustのコードを書いていれば誰でもエラーが出ることはあるでしょうから、その度にエラーメッセージを見てデバッグしていくうちに体で所有権を理解した方が良いと思います。

著者
大西 武 (オオニシ タケシ)
1975年香川県生まれ。大阪大学経済学部経営学科中退。プログラミング入門書など30冊以上を商業出版。Microsoftで大賞やNTTドコモでグランプリなど20回以上全国区のコンテストに入賞。オリジナルの間違い探し「3Dクイズ」が全国放送のTVで約10回出題。

連載バックナンバー

開発言語技術解説
第15回

「TAURI」でデータベースを使ってみよう

2024/5/10
第15回の今回は「TAURI」でオープンソースのデータベース「SQLite3」を使用して、テーブル表に表示する解説をしていきます。
開発言語技術解説
第14回

「TAURI」で気象庁の「CSVデータ」を解析する

2024/5/1
第14回の今回は気象庁のWebサイトから指定した地域の1年間の気象データをダウンロードして「TAURI」で解析していきます。
開発言語技術解説
第13回

「TAURI」で「簡易RSSリーダー」を開発してみよう

2024/4/16
第13回の今回は「TAURI」で「RSSフィード」を読み込んでWebページに一覧表示し、リンクのページを開くための新規ウィンドウを作成するところまでを解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています