How to change a value while dropping?

2019-09-01 15:39发布

I’m currently trying to understand how drop works. The following code crashes and I don’t understand why. From my understanding, the usage of std::ptr::write should prevent the destructor (edit: of the original value, here: Rc) from running (in this case nothing bad should happen beside the memory leak). But it doesn’t seem to do that as (playpen, compile with -O0)

use std::rc::Rc;
use std::mem;
use std::ptr;

enum Foo {
    Bar(Rc<usize>),
    Baz
}
use Foo::*;

impl Drop for Foo {
    fn drop(&mut self) {
        match *self {
            Bar(_) => {
                unsafe { ptr::write(self, Foo::Baz) }
                //unsafe { mem::forget(mem::replace(self, Foo::Baz)) }
            }
            Baz => ()
        }
    }
}

fn main() {
    let _ = Foo::Bar(Rc::new(23));
}

gives an overflow error:

thread '<main>' panicked at 'arithmetic operation overflowed', /Users/rustbuild/src/rust-buildbot/slave/nightly-dist-rustc-mac/build/src/liballoc/rc.rs:755

The other variant quits with an illegal instruction. Why does that happen? How can I replace self with a value that will be properly dropped?

标签: rust
1条回答
Viruses.
2楼-- · 2019-09-01 15:41

I'm not sure yet how to accomplish your goal, but I can show that

From my understanding, the usage of std::ptr::write should prevent the destructor from running

isn't true:

use std::mem;
use std::ptr;

struct Noisy;
impl Drop for Noisy {
    fn drop(&mut self) { println!("Dropping!") }
}

enum Foo {
    Bar(Noisy),
    Baz
}
use Foo::*;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("1");

        match self {
            &mut Bar(_) => {
                println!("2");
                unsafe { ptr::write(self, Foo::Baz) }
                println!("3");
            }
            &mut Baz => {
                println!("4");
            }
        }

        println!("5");
    }
}

fn main() {
    let _ = Foo::Bar(Noisy);
}

This prints:

1
2
3
5
Dropping!

Indicating that the destructor for Foo::Bar is still being run, including the destructor of Noisy.

A potential solution is to use Option::take:

use std::mem;

struct Noisy;
impl Drop for Noisy {
    fn drop(&mut self) { println!("Dropping!") }
}

enum Foo {
    Bar(Option<Noisy>),
    Baz
}

impl Drop for Foo {
    fn drop(&mut self) {
        match *self {
            Foo::Bar(ref mut x) => {
                unsafe { mem::forget(x.take()) }
            }
            Foo::Baz => {}
        }
    }
}

fn main() {
    let _ = Foo::Bar(Some(Noisy));
}
查看更多
登录 后发表回答