所有権なしポインタ
前回の投稿では、所有権ありポインタを紹介した。今回は、Rustのプログラムではるかに一般的な他のポインタについて語ろう。所有権なしポインタ(所有権なしの参照または単に参照とも呼ばれる)だ。
既存のデータに対する参照を作りたい(所有権ありポインタのようにヒープ領域にデータを確保し、そのデータを参照するのではない)場合、&記号(所有権なし参照)を使う必要がある。
所有権ありポインタとなしポインタがRustにおいて、最も一般的なポインタと言えるだろう。そして、C++のポインタや参照に当たるものが必要なら(例えば、関数に引数を参照渡しするなど)、所有権なしポインタがこれに当たるかもしれない。
&演算子を使用して所有権なしポインタを作ったり、参照型を表し、*演算子でその値を取る。所有権ありポインタと同じ、自動被参照のルールが適用される。
fn foo() { let x = &3; // タイプ: &i32 let y = *x; // 3, タイプはi32 bar(x, *x); bar(&y, y); } fn bar(z: &i32, i: i32) { // ... }&演算子はメモリー割り当てを行わない(既存のデータに対する所有権なし参照しか作成できない)。そのため、所有権なし参照がスコープ外に抜けても、メモリー解放は行われない。
所有権なし参照は、固有ではない。換言すれば、同じ値を指す参照を複数作れるということだ。
fn foo() { let x = 5; // タイプ: i32 let y = &x; // タイプ: &i32 let z = y; // タイプ: &i32 let w = y; // タイプ: &i32 println!("全部5のはず: {} {} {}", *w, *y, *z); }値同様、所有権なし参照も標準では不変である。
&mut演算子を使用して可変な参照を取ったり、可変参照タイプを表したりできる。可変所有権なし参照は固有である(一つの値を指す可変参照は一つしか作れず、可変参照を作れるのは不変な参照がないときに限る)。不変参照が必要な箇所には可変参照を当てはめることができるが、その逆は不可能である。
さて、上記の性質を例でおさらいしてみよう。
fn bar(x: &i32) { ... } fn var_mut(x: &mut i32) { ... } // &mut i32は可変なi32型の値に対する参照 fn foo() { let x = 5; //let xr = &mut x; // エラー: 不変変数に対して可変参照は作れない let xr = &x; // OK(不変な参照を作成) bar(xr); //bar_mut(xr); // エラー: 可変参照が必要 let mut x = 5; let xr = &x; // OK(不変な参照を作成) //*xr = 4; // エラー: 不変参照を可変化しようとしている //let xr = &mut x; // エラー: すでに不変参照が存在するので、可変参照は作れない let mut x = 5; let xr = &mut x; // OK(可変参照を作成) *xr = 4; // OK //let xr = &x; // エラー: すでに可変参照があるので、不変参照は作れない //let xr = &mut x; // エラー: 同時に二つ以上可変参照は存在できない bar(xr); // OK bar_mut(xr); // OK }参照自体は、参照を保持する変数の可変性にかかわらず、可変にも不変にもなることに注目してほしい。これは、C++において、ポインタが、参照するデータの可変性にかかわらず、定性になったり不定性になったりすることと似ている。
また、所有権ありポインタとは対称的である。なぜなら、所有権ありポインタの可変性は、データに紐付いているからだ。
fn foo() { let mut x = 5; let mut y = 6; let xr = &mut x; //xr = &mut y; // エラー: xrは不変 let mut x = 5; let mut y = 6; let mut xr = &mut x; xr = &mut y; // OK let mut x = 5; let mut y = 6; let mut xr = &x; xr = &y; // OK: 参照されているデータは可変ではないのに、xrは可変 }可変な値を所有権なし参照すると、参照が存在する間だけ不変になる。一旦、所有権なしポインタがスコープ外になると、先ほどの値は再び可変になる。この挙動も、所有権ありポインタと対称的だ。所有権ポインタは、所有権が移ってしまったら、前の参照は二度と使用できない。
fn foo() { let mut x = 5; // タイプ: i32 { let y = &x; // タイプ: &i32 //x = 4; // エラー: xは参照中 println!("{}", x); // OK: xの読み出しは可能 } x = 4; // OK: yはもう存在しない }値に対して、可変な参照を取ろうとした場合にも同じ事態に遭遇する。それでも、値は変更できないのだ。
一般にRustでは、データは一つの変数または、ポインタ経由でしか変更できない。
その他、今度は可変参照を保持しているので、不変参照を取ることはできない。これは、対象の値の使用法の制限になる。
fn foo() { let mut x = 5; // タイプ: i32 { let y = &mut x; // タイプ: &mut i32 //x = 4; // エラー: xは参照中 //println!("{}", x); // エラー: xを参照する必要がある } x = 4; // OK: yはもう存在しない }C++と異なり、Rustでは値は自動的に参照されることはない。ゆえに、参照渡しの引数を持った関数がある場合、呼び出し側で実引数を参照しなければならない。とは言ったが、ポインタ型は、参照に自動変換される。
fn foo(x: &i32) { ... } fn bar(x: i32, y: Box<i32>) { foo(&x); // foo(x); // エラー: i32ではなく、&i32が必要 foo(y); // OK foo(&*y); // これもOK。より明確だが、いい書き方ではない }
mut vs const
この段階で、Rustのmut属性とC++のconst属性は比較する価値があるだろう。
一言で言うと、両者は真逆である。
Rustにおいて、値は標準で不変であり、mut属性を使って可変にすることができる。
一方、C++において、値は標準で可変であり、const属性を使用して定数にできる。
より気付きにくいが重要な差異は、C++における定性は、1回限りしか有効でないのに対して、Rustの不変性は普遍であるということだ。したがって、C++でconstな変数を定義したとしても、誰か別の人がその変数へのconstでない参照を取って、定義者に知られずに変更することができる。しかし、Rustでは不変な変数を定義したら、二度と変わらないと保障されている。
前述したように、可変な変数はすべて固有である。そのため、可変な値があったら、自分で変えない限り、変わることはない。また、誰も、それが不変であることを頼りにしていないとわかってるので、自由に変更することができる。
所有権なし参照とライフタイム(生存期間)
Rustの安全性に関する大きな目的は、nullポインタ(ポインタが削除されたメモリー領域を指すこと)を避けることだ。Rustでは、無効な参照は存在しえない。参照自体よりも長生きするメモリー領域を所有権なし参照することだけが有効なのだ(まあ、少なくとも参照である限りは)。別の言い方をすると、参照の生存期間は、参照される値の生存期間よりも短くなければならないということだ。
上記の制限は、今までの例すべてで守られている。
{}記号で導入されるスコープや、関数は生存期間に基づいて紐付いている。つまり、変数がスコープ外に抜けると、そのライフタイムが終了したことと等しいということである。
自分よりも生存期間が短いものへの参照を生成しようとする(例として、スコープが短い場合など)と、コンパイラがエラーを吐く。
fn foo() { let x = 5; let mut xr = &x; // OK: 変数xと変数xrは生存期間が同じ { let y = 6; //xr = &y; // エラー: 変数xrは変数yよりも生存期間が長い } // 変数yはここで解放される } // 変数x,xrはここで解放される上記の例で変数x,xrは変数xrの方が下に記述されているので、生存期間が異なるが、より気をひくのは生存期間の終了のタイミングである。なぜなら、どんなに頑張っても存在しない変数は参照できないからだ。Rustにより強制され、C++よりも安全性を高められる事柄である。
ライフタイムの明示
しばし、所有権なし参照を試してみた結果、ライフタイムが明示された所有権なしポインタに行き当たったことがあるだろう。
これは、&a' T(比較: &T)と書き、別に記事を立ち上げるほどのビッグトピックになる類の話だ。ちょうど、生存期間の多様性を説明しないといけないしね(まあ、その前にもっと一般的でないポインタの話があるけど)。
ひとまず、&Tは&'a Tの略記であり、aは型Tが宣言されているスコープを指すとだけ述べておきたい。
原文: https://github.com/nrc/r4cppp/blob/master/borrowed.md
0 件のコメント:
コメントを投稿
なにか意見や感想、質問などがあれば、ご自由にお書きください。