借用ポインター
前回の投稿では、一意ポインターを紹介しました。今回は、ほとんどの Rust プログラムではるかに一般的な別の種類のポインター、借用ポインター(別名、借用参照、または単に参照)について説明します。
既存の値への参照を持ちたい場合(一意ポインターのようにヒープ上に新しい値を作成してそれを指すのではなく)、借用参照である & を使用する必要があります。これはおそらく Rust で最も一般的な種類のポインターであり、C++ のポインターや参照の代わりになるもの(たとえば、関数にパラメーターを参照渡しするためのもの)が必要なら、おそらくこれです。
借用参照を作成したり参照型を示したりするには & 演算子を使用し、それらを逆参照するには * を使用します。自動逆参照に関する同じ規則が、一意ポインターの場合と同様に適用されます。例を示します。
#![allow(unused)] fn main() { fn foo() { let x = &3; // 型: &i32 let y = *x; // 3、型: i32 bar(x, *x); bar(&y, y); } fn bar(z: &i32, i: i32) { // ... } }
& 演算子はメモリを割り当てません(既存の値への借用参照しか作成できません)。また、借用参照がスコープを外れても、メモリは削除されません。
借用参照は一意ではありません。同じ値を指す複数の借用参照を持つことができます。たとえば、次のようになります。
#![allow(unused)] fn main() { fn foo() { let x = 5; // 型: i32 let y = &x; // 型: &i32 let z = y; // 型: &i32 let w = y; // 型: &i32 println!("These should all be 5: {} {} {}", *w, *y, *z); } }
値と同様に、借用参照はデフォルトで不変です。&mut を使って可変参照を取得したり、可変参照型を表したりすることもできます。可変の借用参照は一意です(値への可変参照は 1 つしか取得できず、不変参照が存在しない場合にのみ可変参照を持つことができます)。不変参照が求められている場所で可変参照を使用することはできますが、その逆はできません。これらをすべてまとめた例を示します。
#![allow(unused)] fn main() { fn bar(x: &i32) { ... } fn bar_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 xr2 = &x; // エラー - すでに可変参照があるため、 // 不変参照は作成できない //let xr2 = &mut x; // エラー - 可変参照は一度に 1 つしか持てない bar(xr); // OK bar_mut(xr); // OK } }
参照が可変かどうかは、その参照を保持している変数の可変性とは独立している場合があることに注意してください。これは、ポインターが指すデータとは独立して、ポインターを const にできる(またはできない)C++ と似ています。これは、ポインターの可変性がデータの可変性に結び付いている一意ポインターとは対照的です。例を示します。
#![allow(unused)] fn main() { 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 x = 5; let y = 6; let mut xr = &x; xr = &y; // OK - 参照先のデータは可変ではないが、xr は mut } }
可変値が借用されると、借用の間は不変になります。借用ポインターがスコープを外れると、その値は再び変更できるようになります。これは、一度ムーブされると二度と使用できない一意ポインターとは対照的です。例を示します。
#![allow(unused)] fn main() { fn foo() { let mut x = 5; // 型: i32 { let y = &x; // 型: &i32 //x = 4; // エラー - x は借用されている println!("{} {}", y, x); // OK - x は読み取り可能 } x = 4; // OK - y はもはや存在しない } }
値への可変参照を取得した場合も、同じことが起こります。その値は依然として変更できません。一般に Rust では、データは常に 1 つの変数またはポインターを介してのみ変更できます。さらに、可変参照を持っているため、不変参照を取得することはできません。これにより、基になる値の使い方が制限されます。
#![allow(unused)] fn main() { 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 は自動的に値への参照を作ってはくれません。そのため、関数が参照でパラメーターを受け取る場合、呼び出し元は実引数への参照を渡さなければなりません。ただし、ポインター型は自動的に参照へ変換されます。
#![allow(unused)] fn main() { fn foo(x: &i32) { ... } fn bar(x: i32, y: Box<i32>) { foo(&x); // foo(x); // エラー - &i32 が期待されたが、i32 が見つかった foo(y); // OK foo(&*y); // これも OK で、より明示的だが、良いスタイルではない } }
mut と const
この段階で、Rust の mut と C++ の const を比較しておく価値がおそらくあります。表面的には、これらは正反対です。Rust では値はデフォルトで不変であり、mut を使うことで可変にできます。C++ では値はデフォルトで可変ですが、const を使うことで定数にできます。より微妙で重要な違いは、C++ の const 性は値の現在の使用にのみ適用されるのに対し、Rust の不変性は値のすべての使用に適用されるという点です。つまり、C++ で私が const 変数を持っている場合、他の誰かがそれへの非 const 参照を持っていて、私が知らないうちにそれが変化する可能性があります。Rust では、不変変数を持っている場合、それが変化しないことが保証されます。
上で述べたように、すべての可変変数は一意です。そのため、可変値を持っている場合、自分が変更しない限りそれが変化しないことがわかります。さらに、それが変化しないことに他の誰も依存していないとわかっているため、自由に変更できます。
借用とライフタイム
Rust の主要な安全性目標の 1 つは、ダングリングポインター(ポインターが指すメモリよりも長く生存してしまうポインター)を避けることです。Rust では、ダングリングした借用参照を持つことは不可能です。参照よりも長く生存する(まあ、少なくとも参照と同じだけ長く生存する)メモリへの借用参照を作成する場合にのみ合法です。言い換えると、参照のライフタイムは参照先の値のライフタイムより短くなければなりません。
これは、この投稿のすべての例で実現されています。{} や関数によって導入される
スコープはライフタイムの境界です。変数がスコープ外に出ると、そのライフタイムは
終わります。より狭いスコープ内など、より短いライフタイムへの参照を取ろうとすると、
コンパイラはエラーを出します。たとえば、
#![allow(unused)] fn main() { fn foo() { let x = 5; let mut xr = &x; // OK - x と xr は同じライフタイムを持つ { let y = 6; xr = &y // エラー - xr は y より長く生存する } // y はここで解放される println!("{:?}", xr); // xr はここで使用されるため、y より長く生存する。この行をコメントアウトしてみてください。 } // x と xr はここで解放される }
上の例では、xr と y は同じライフタイムを持ちません。なぜなら、y は xr より後に 始まるからです。しかし、より興味深いのはライフタイムの終わりのほうです。いずれにせよ、 変数が存在する前にその変数を参照することはできないからです。これは Rust が強制する もう 1 つのことであり、Rust を C++ より安全にしているものです。
明示的なライフタイム
借用ポインタをしばらく扱っていると、おそらく明示的なライフタイムを持つ借用ポインタに
出会うでしょう。これらは &'a T(参照
&T)という構文を持ちます。ライフタイムポリモーフィズムも同時に扱う必要があるため、
これはかなり大きなトピックです。そのため、別の投稿に回すことにします(ただし、その前に
扱うべき、もう少し一般的でないポインタ型がいくつかあります)。今のところは、&T は
&'a T の省略形であり、ここで a は現在のスコープ、つまりその型が宣言されている
スコープである、ということだけを述べておきます。