借用チェック
Rust の 借用チェッカー は、値を借用する方法に制約を課します。 参照が、それが借用している値を 超えて生存する ことはできないことは、すでに見ました。
// Copyright 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 fn main() { let x_ref = { let x = 10; &x }; dbg!(x_ref); }
借用チェッカーが強制する、もう 1 つの主要なルールもあります。エイリアシング ルールです。ある値については、どの時点でも次のいずれか一方のみが成り立ちます。
- その値への共有参照を 1 つ以上持つことができる、または
- その値への排他的参照をちょうど 1 つだけ持つことができます。
// Copyright 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 fn main() { let mut a = 10; let b = &a; { let c = &mut a; *c = 20; } dbg!(a); dbg!(b); }
- 「outlives」ルールは、最初に参照を見たときにすでに示しました。ここで 改めて取り上げるのは、借用チェックが借用を検証するためにいくつか異なる ルールに従っていることを受講者に示すためです。
- 上のコードがコンパイルできないのは、
aが同時に可変として(cを通じて) 借用され、不変として(bを通じて)借用されているからです。- 要件は、競合する参照が同じ時点に 存在しない ことだという点に注意して ください。参照がどこでデリファレンスされるかは関係ありません。
*c = 20をコメントアウトして、cを一度も使わなくてもコンパイラエラーが 依然として発生することを示してください。 - 借用の競合を発生させるのに、中間の参照
cは必須ではない点にも注意して ください。cをaへの直接の変更に置き換え、それでも同様のエラーが 発生することを示してください。これは、値を直接変更すると実質的に一時的な 可変参照が作られるためです。
- 要件は、競合する参照が同じ時点に 存在しない ことだという点に注意して ください。参照がどこでデリファレンスされるかは関係ありません。
- コードをコンパイルできるようにするには、
bのdbg!文をcを導入する スコープより前に移動してください。- その変更を行うと、コンパイラは
bがcを通じたaの新しい可変借用より 前でしか使われないことを認識します。これは借用チェッカーの 「非字句的ライフタイム」と呼ばれる機能です。
- その変更を行うと、コンパイラは
さらに詳しく
- 厳密には、あるデータに対する複数の可変参照は、再借用によって同時に存在 できます。これにより、元の参照を無効にすることなく、可変参照を関数に渡す ことができます。この Playground の例 はその挙動を示しています。
- Rust は、排他的参照の制約を使って、マルチスレッドコードでデータ競合が発生 しないことを保証します。これは、一度に 1 つのスレッドしかデータの一部に 可変アクセスできないためです。
- Rust はこの制約をコードの最適化にも利用します。たとえば、共有参照が指す値は、 その参照のライフタイムの間、安全にレジスタにキャッシュできます。
- 構造体のフィールドは互いに独立して借用できますが、構造体のメソッドを 呼び出すと構造体全体が借用され、個々のフィールドへの参照が無効になる可能性が あります。この Playground スニペット にその例があります。