Drop
トレイトのライフタイムへの影響について。
(注: Drop
トレイトを直接的に扱っていなくても考慮が必要。)
Drop
トレイトの実装の有無は周辺のライフタイムなどに影響を与える。
そのため、Drop
トレイトを実装しただけでコンパイルエラーになる事がある。
参照を含む型が Drop
トレイトを実装する場合について。その型による参照はその使用が終了した時点では解放されず、スコープが終了し drop
が呼ばれるまで維持される。このライフタイムの延長により、参照の制限を受ける範囲が増える。
Drop
が見える範囲になくてもこのパターンになりうる。なぜなら、その型そのものが Drop
を実装していなくても、そのフィールドの型が同じように参照を含んで Drop
を実装しているならば、親の型にその影響が派生する。
例えば、std
の Ref
は Drop
を実装しない。しかし、そのフィールドは Drop
を実装する。そのため、Ref
はこのパターンの原因になりうる。
このパターンは以下のエラーにつながる。
Drop
あり)
以下では、_refs
のライフタイムが延長されている。
fn main() {
let mut var = 1;
let _refs = MyRef(&var);
var = 2;
dbg!(var);
}
struct MyRef<'a>(&'a i32);
impl Drop for MyRef<'_> {
fn drop(&mut self) {
println!("ID [{}] is drpoped.", self.0)
}
}
error[E0506]: cannot assign to `var` because it is borrowed --> src\main.rs:4:5 | 3 | let _refs = MyRef(&var); | ---- `var` is borrowed here 4 | var = 2; | ^^^^^^^ `var` is assigned to here but it was already borrowed 5 | dbg!(var); 6 | } | - borrow might be used here, when `_refs` is dropped and runs the `Drop` code for type `MyRef`
Drop
なし)
以下では、MyRef1
には Drop
が実装されていない。
fn main() {
let mut var = 1;
let _refs = MyRef1::new(&var);
var = 2;
dbg!(var);
}
struct MyRef1<'a>(MyRef2<'a>);
impl<'a> MyRef1<'a> {
pub fn new(refs: &'a i32) -> Self {
Self(MyRef2(refs))
}
}
struct MyRef2<'a>(&'a i32);
impl Drop for MyRef2<'_> {
fn drop(&mut self) {
println!("ID [{}] is drpoped.", self.0)
}
}
error[E0506]: cannot assign to `var` because it is borrowed --> src\main.rs:4:5 | 3 | let _refs = MyRef1::new(&var); | ---- `var` is borrowed here 4 | var = 2; | ^^^^^^^ `var` is assigned to here but it was already borrowed 5 | dbg!(var); 6 | } | - borrow might be used here, when `_refs` is dropped and runs the destructor for type `MyRef1<'_>`
参照を含む型が Drop
トレイトを実装する場合について。その参照先には同じタイミングで解放されるものを指定できない。そして、借用チェッカーはフィールドの解放順 (定義の逆順) までは見ない。つまり、問題の型のフィールドと兄弟関係にあるフィールドの参照は指定できなくなる。そうでなければ、drop
実行時に参照先の有無が保証できなくなってしまう。
Drop
が未使用でもこのパターンになりうる。なぜなら、トレイトが抽象化した型が Drop
を実装している可能性がある。そのため、それらの用法も予めエラーにする必要がある。なお、この場合のトレイトによる抽象化は動的になる (静的なら型の全構造が分かるため、どこで Drop
が使用されているかを完全に把握できる)。
Drop
あり)
以下では、drop
の時点で &data.id
はダングリングポインタかもしれない。
fn main() {
let mut data = MyData::default();
data.on_drop = Some(OnDrop(&data.id));
}
#[derive(Default)]
struct MyData<'a> {
id: i32,
on_drop: Option<OnDrop<'a>>,
}
struct OnDrop<'a>(&'a i32);
impl<'a> Drop for OnDrop<'a> {
fn drop(&mut self) {
println!("ID [{}] is drpoped.", self.0)
}
}
error[E0597]: `data.id` does not live long enough --> src\main.rs:3:38 | 2 | let mut data = MyData::default(); | -------- binding `data` declared here 3 | data.on_drop = Some(OnDrop(&data.id)); | ^^^^^^^^ borrowed value does not live long enough 4 | } | - | | | `data.id` dropped here while still borrowed | borrow might be used here, when `data` is dropped and runs the destructor for type `MyData<'_>`
Drop
なし)
以下では、MyTrait
の具象型が Drop
を実装する可能性がある。
fn main() {
let mut data = MyData::default();
data.my_trait = Some(Box::new(MyType(&data.id)));
data.my_trait.unwrap().print_id();
}
#[derive(Default)]
struct MyData<'a> {
id: i32,
my_trait: Option<Box<dyn MyTrait<'a> + 'a>>,
}
trait MyTrait<'a> {
fn print_id(&self);
}
struct MyType<'a>(&'a i32);
impl<'a> MyTrait<'a> for MyType<'a> {
fn print_id(&self) {
println!("ID is [{}].", self.0);
}
}
error[E0597]: `data.id` does not live long enough --> src\main.rs:3:42 | 2 | let mut data = MyData::default(); | -------- binding `data` declared here 3 | data.my_trait = Some(Box::new(MyType(&data.id))); | ^^^^^^^^ borrowed value does not live long enough 4 | data.my_trait.unwrap().print_id(); 5 | } | - | | | `data.id` dropped here while still borrowed | borrow might be used here, when `data` is dropped and runs the destructor for type `MyData<'_>`
参照を含む型が Drop
トレイトを実装する場合について。ブロック式の末尾にその型があり、かつそれがブロック内のローカル変数を参照しているとエラーになる。なぜなら、ブロック内のローカル変数はブロック式の末尾よりも先に解放される。そのため、drop
実行時に参照先が存在しなくなってしまう。
Drop
が未使用でもこのパターンになりうる。なぜなら、トレイトが抽象化した型が Drop
を実装している可能性がある。そのため、それらの用法も予めエラーにする必要がある。 (抽象化が静的か動的かは問わない)。
後述のエラーメッセージの help
にある通り、ブロック式の末尾をローカル変数として切り出せばよい。これにより、値の破棄順序を逆転できる。
Drop
あり)
以下では、OnDrop(&local)
がブロック式の末尾にある。
しかし、その drop
の時点で local
はすでに破棄されている。
fn main() {
let val = {
let local = 42;
*OnDrop(&local).0
};
println!("{}", val);
}
struct OnDrop<'a>(&'a i32);
impl Drop for OnDrop<'_> {
fn drop(&mut self) {
println!("ID [{}] is drpoped.", self.0)
}
}
error[E0597]: `local` does not live long enough --> src\main.rs:4:11 | 3 | let local = 42; | ----- binding `local` declared here 4 | *OnDrop(&local).0 | -------^^^^^^- | | | | | borrowed value does not live long enough | a temporary with access to the borrow is created here ... 5 | }; | -- ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `OnDrop` | | | `local` dropped here while still borrowed | = note: the temporary is part of an expression at the end of a block; consider forcing this temporary to be dropped sooner, before the block's local variables are dropped help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block | 4 | let x = *OnDrop(&local).0; x | +++++++ +++
Drop
なし)
以下では、MyTrait
の具象型が Drop
を実装する可能性がある。
fn main() {
let val = {
let local = 42;
MyType(&local).as_trait().val()
};
println!("{}", val);
}
trait MyTrait {
fn val(&self) -> i32;
}
struct MyType<'a>(&'a i32);
impl<'a> MyType<'a> {
fn as_trait(self) -> impl MyTrait + 'a {
self
}
}
impl<'a> MyTrait for MyType<'a> {
fn val(&self) -> i32 {
*self.0
}
}
error[E0597]: `local` does not live long enough --> src\main.rs:4:16 | 3 | let local = 42; | ----- binding `local` declared here 4 | MyType(&local).as_trait().val() | -------^^^^^^------------ | | | | | borrowed value does not live long enough | a temporary with access to the borrow is created here ... 5 | }; | -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl MyTrait + '_` | | | `local` dropped here while still borrowed | = note: the temporary is part of an expression at the end of a block; consider forcing this temporary to be dropped sooner, before the block's local variables are dropped help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block | 4 | let x = MyType(&local).as_trait().val(); x | +++++++ +++
dropck_eyepatch
は まだ不安定な機能だが、ここでのエラーの対策になるよう予定されている。具体的には、drop
メソッドの実行時点で参照がダングリングポインタになってる可能性を指摘できるようにする。詳しくは Rustnomicon を参照 (それによると将来的にはより洗練された機構が提供されそうな気配…)。