ドロップチェック
通常、ローカルが使用されるときには常に、そのローカルの型が整形式であることを要求します。これには、ローカルの where 境界を証明することが含まれ、さらにそのローカルによって使用されるすべてのリージョンがライブであることも要求されます。
唯一の例外は、値がスコープ外に出るときに暗黙的にドロップされる場合です。これは、必ずしも値がライブであることを要求しません。
fn main() {
let x = vec![];
{
let y = String::from("I am temporary");
x.push(&y);
}
// `x` はここでスコープ外に出ます。これは `y` への参照が
// 無効化された後です。つまり、`x` をドロップしている間、その型は
// ライブではないリージョンを含んでいるため、整形式ではありません。
}
これは、値をドロップしても死んだリージョンにアクセスしようとしない場合にのみ健全です。これを確認するために、その値の型が drop-live であることを要求します。
その要件は fn dropck_outlives で計算されます。
このセクションの残りでは、リージョンパラメーターがライブであることを要求する型として、次の型定義を使用します。
#![allow(unused)]
fn main() {
struct PrintOnDrop<'a>(&'a str);
impl<'a> Drop for PrintOnDrop<'_> {
fn drop(&mut self) {
println!("{}", self.0);
}
}
}
値がどのようにドロップされるか
本質的には、型 T の値は、その「ドロップグルー」を実行することでドロップされます。ドロップグルーはコンパイラによって生成され、最初に <T as Drop>::drop を呼び出し、その後、再帰的に所有されている値のドロップグルーを再帰的に呼び出します。
Tに明示的なDropimpl がある場合、<T as Drop>::dropを呼び出します。TがDropを実装しているかどうかに関係なく、Tによって 所有されている すべての値へ再帰します。- 参照、生ポインター、関数ポインター、関数アイテム、トレイトオブジェクト1、およびスカラーは何も所有しません。
- タプル、スライス、配列は、その要素を所有されているものとみなします。 長さ 0 の配列については、その要素型の値を一切所有しません。
- ADT のすべてのフィールド(すべてのバリアントのもの)は所有されているとみなされます。
enum についてはすべてのバリアントを考慮します。ここでの例外は
ManuallyDrop<U>であり、これはUを所有しているとはみなされません。PhantomData<U>も何も所有しません。 クロージャとジェネレーターは、キャプチャした upvar を所有します。
ある型がドロップグルーを持つかどうかは、fn Ty::needs_drop によって返されます。
ローカルを部分的にドロップする
自分自身では Drop を実装していない型については、残りをドロップする前に値の一部を部分的に move することもできます。この場合、まだ move されていない値に対するドロップグルーのみが呼び出されます。例:
fn main() {
let mut x = (PrintOnDrop("third"), PrintOnDrop("first"));
drop(x.1);
println!("second")
}
MIR の構築中には、ローカルの型が drop を必要とする限り、そのローカルはスコープ外に出るときに常にドロップされる可能性があると仮定します。変数に対する正確なドロップグルーの計算は、borrowck の後に ElaborateDrops パスで行われます。つまり、ローカルの一部が以前にドロップされていたとしても、dropck は依然としてこの値がライブであることを要求します。これは、ローカルを完全に move した場合でも同様です。
fn main() {
let mut x;
{
let temp = String::from("I am temporary");
x = PrintOnDrop(&temp);
drop(x);
}
} //~ ERROR `temp` の存続期間が十分に長くありません。
borrowck の前にある程度のドロップ展開を追加できるようにすれば、この例をコンパイルできるようになるはずです。const チェックの前にドロップ展開を移動するための不安定な機能があります: #73255。borrowck の前にいくらかのドロップ展開を行うためのそのような feature gate は存在しませんが、関連する MCP はあります。
dropck_outlives
私たちが実行する「liveness」の計算には、2 つの異なるものがあります。
- 値
vは、場所Lで、その後に「使用」される可能性がある場合、use-live です。ここでの use は、基本的に drop ではないものすべてです - 値
vは、場所Lで、その後にドロップされる可能性がある場合、drop-live です
ものが use-live である場合、その型全体が L で有効でなければなりません。ものが drop-live である場合、dropck によって要求されるすべてのリージョンが L で有効でなければなりません。MIR でドロップされる値は places です。
ある型に対して dropck_outlives によって計算される制約は、その型に対して生成されるドロップグルーとよく対応しています。ドロップグルーとは異なり、dropck_outlives は所有されている値そのものではなく、所有されている値の型を気にします。型 T の値については、次のようになります。
Tに明示的なDropがある場合、すべてのジェネリック引数がライブであることを要求します。ただし、それらが#[may_dangle]でマークされている場合は、完全に無視されますTに明示的なDropがあるかどうかに関係なく、Tによって 所有されている すべての型へ再帰します- 参照、生ポインター、関数ポインター、関数アイテム、トレイトオブジェクト1、およびスカラーは何も所有しません。
- タプル、スライス、配列は、その要素型を所有されているものとみなします。 配列については、現在その長さが 0 かどうかをチェックしていません。
- ADT のすべてのフィールド(すべてのバリアントのもの)は所有されているとみなされます。
ここでの例外は
ManuallyDrop<U>であり、これはUを所有しているとはみなされません。私たちはPhantomData<U>がUを所有しているとみなします。 - クロージャとジェネレーターは、キャプチャした upvar を所有します。
太字で示したセクションは、dropck_outlives が、Ty::needs_drop では無視される型を所有されているものとみなすケースです。私たちは、それを含むローカルに対する Ty::needs_drop が true を返した場合にのみ dropck_outlives に依存します。これは、liveness 要件が、ある型がより大きなローカルに含まれているかどうかに応じて変わり得ることを意味します。これは一貫しておらず、修正されるべきです: 配列の例と PhantomData の例 があります。2
これらの不整合を修正する 1 つの可能な方法は、MIR の構築をより悲観的にすることです。おそらく Ty::needs_drop をより弱くするか、あるいは dropck_outlives をより精密に変更して、ライブであることを要求されるリージョンを少なくすることです。