Drop トレイトのライフタイムへの影響について。
(注: Drop トレイトを直接的に扱っていなくても考慮が必要。)

概要

Drop トレイトの実装の有無は周辺のライフタイムなどに影響を与える。

そのため、Drop トレイトを実装しただけでコンパイルエラーになる事がある。

パターン

パターン A

ライフタイムの延長

参照を含む型が Drop トレイトを実装する場合について。その型による参照はその使用が終了した時点では解放されず、スコープが終了し drop が呼ばれるまで維持される。このライフタイムの延長により、参照の制限を受ける範囲が増える。

注意点

Drop が見える範囲になくてもこのパターンになりうる。なぜなら、その型そのものが Drop を実装していなくても、そのフィールドの型が同じように参照を含んで Drop を実装しているならば、親の型にその影響が派生する。

例えば、stdRefDrop を実装しない。しかし、そのフィールドは 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<'_>`

パターン B

兄弟フィールドの参照防止

参照を含む型が Drop トレイトを実装する場合について。その参照先には同じタイミングで解放されるものを指定できない。そして、借用チェッカーはフィールドの解放順 (定義の逆順) までは見ない。つまり、問題の型のフィールドと兄弟関係にあるフィールドの参照は指定できなくなる。そうでなければ、drop 実行時に参照先の有無が保証できなくなってしまう。

注意点

Drop が未使用でもこのパターンになりうる。なぜなら、トレイトが抽象化した型が Drop を実装している可能性がある。そのため、それらの用法も予めエラーにする必要がある。なお、この場合のトレイトによる抽象化は動的になる (静的なら型の全構造が分かるため、どこで Drop が使用されているかを完全に把握できる)。

関連エラー

このパターンは E0597特定パターンにつながる。

サンプル (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<'_>`

パターン C (改良済)

ブロック式の末尾処理
[旧情報]

参照を含む型が Drop トレイトを実装する場合について。ブロック式の末尾にその型があり、かつそれがブロック内のローカル変数を参照しているとエラーになる。なぜなら、ブロック内のローカル変数はブロック式の末尾よりも先に解放される。そのため、drop 実行時に参照先が存在しなくなってしまう。

注意点

Drop が未使用でもこのパターンになりうる。なぜなら、トレイトが抽象化した型が Drop を実装している可能性がある。そのため、それらの用法も予めエラーにする必要がある。 (抽象化が静的か動的かは問わない)。

解決策

後述のエラーメッセージの help にある通り、ブロック式の末尾をローカル変数として切り出せばよい。これにより、値の破棄順序を逆転できる。

関連エラー

このパターンは E0597特定パターンにつながる。

サンプル (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

dropck_eyepatchまだ不安定な機能だが、ここでのエラーの対策になるよう予定されている。具体的には、drop メソッドの実行時点で参照がダングリングポインタになってる可能性を指摘できるようにする。詳しくは Rustnomicon を参照 (それによると将来的にはより洗練された機構が提供されそうな気配…)。