ドロップフラグ
前章の例では、 Rust における興味深い問題を紹介しました。 状況によって、メモリの場所を初期化したり、初期化されていない状態に戻したり、 再初期化したりすることを、完全に安全に行なうことが可能だということを 確認してきました。 Copy を実装している型に関しては、メモリの場所にあるものは 単なるビットのランダムな山であるため、これは特に重要なことではありません。 しかし、デストラクタを備えている型に関しては話が違います。 Rust は変数が代入されたときや、 あるいは変数がスコープを外れたときは毎回、デストラクタを呼ぶかを知る必要があります。 これを、状況に応じた初期化と共に、どのように行えばよいのでしょうか?
全ての代入において心配する必要がある問題ではないことに注意してください。
特に、参照外しを通した代入では、状況によらずドロップしますし、 let
を使用した
代入では、状況によらずドロップしません。
let mut x = Box::new(0); // let によって新しい変数が生成されるので、ドロップの必要はありません
let y = &mut x;
*y = Box::new(1); // 参照外しでは、参照される側の変数は初期化されていると見なされているため、この参照されている変数はいつもドロップします
これは、以前に初期化された変数や、その副フィールドの 1 つを上書きする時のみ問題となります。
実際には Rust は実行時に、型がドロップされるべきかそうでないかを追っていると分かります。 変数が初期化されたり、初期化されてない状態になったりすると、その変数に対するドロップフラグが 切り替わります。もし変数がドロップされる必要があるかもしれない状況になると、 本当にドロップされるべきかを決定するため、このフラグが評価されます。
勿論、しばしば値の初期化に関する状態は、プログラムのどの地点においても 知ることが出来ます。もしこれが本当なら、コンパイラは理論的には、 もっと効率的なコードを生成できます! 例えば、分岐のない真っ直ぐなコードは、 このような静的ドロップセマンティクスを持っています。
#![allow(unused)] fn main() { let mut x = Box::new(0); // x は初期化されていないので、単に上書きします。 let mut y = x; // y は初期化されていないので、単に上書きします。そして x を初期化前の状態にします。 x = Box::new(0); // x は初期化されていないので、単に上書きします。 y = x; // y は初期化されているので、 y をドロップし、上書きし、そして x を初期化前の状態にします! // y はスコープを抜けました。 y は初期化されているので、 y をドロップします! // x はスコープを抜けました。 x は初期化されていないので、何もしません。 }
同じように、全ての分岐が初期化の点において、同一のことをする分岐があるコードでは、 静的ドロップセマンティクスを持っています。
#![allow(unused)] fn main() { let condition = true; let mut x = Box::new(0); // x は初期化されていないので、単に上書きします。 if condition { drop(x) // x はムーブされたので、 x を初期化前の状態にします。 } else { println!("{}", x); drop(x) // x はムーブされたので、 x を初期化前の状態にします。 } x = Box::new(0); // x は初期化されていない状態なので、単に上書きします。 // x はスコープを抜けました。 x は初期化されているので、 x をドロップします! }
しかしながら以下のようなコードでは、正しくドロップするために実行時の情報が必要となります。
#![allow(unused)] fn main() { let condition = true; let x; if condition { x = Box::new(0); // x は初期化されていないので、単に上書きします。 println!("{}", x); } // x はスコープを抜けました。 x は初期化されていないかもしれません。 // フラグを確認! }
勿論この場合、静的ドロップセマンティクスを復活させるのは些細なことです。
#![allow(unused)] fn main() { let condition = true; if condition { let x = Box::new(0); println!("{}", x); } }
ドロップフラグはスタック上で追跡され、ドロップを実装している型に 隠されることはもはやありません。