为什么滴胶有时不会运行?

时间:2015-01-16 01:43:49

标签: rust

我在其他情况下发生过这种情况,但不能重复。

基本上,有时当你有一个*mut Foo,并且使用Foo将其变成ptr::read()时,有时你的掉胶似乎不会运行。

这是一个可重复的示例,它在不使用FFI的情况下在游戏围栏上运行(这是您将原始指针提升回对象的主要原因):http://is.gd/X6vdAK

use std::ptr::read;
use std::mem::forget;

#[repr(C)]
struct Foo {
  id: i32,
  dispose:Option<Box<Fn(Foo) + 'static>> //'
}

impl Foo {
  pub fn new(id:i32) -> Foo {
    return Foo {
        id: id,
        dispose: None
    };
  }
  pub fn release(mut self) {
    if self.dispose.is_some() {
      self.dispose.take().unwrap()(self);
    }
  }
}

impl Drop for Foo {
  fn drop(&mut self) {
    println!("Discarding a foo with dispose of {:?}", self.dispose.is_some());
  }
}

#[repr(C)]
struct Goblin {
  foo:Foo,
  angry: bool,
  friends: i32
}

#[repr(C)]
struct Bat {
  foo:Foo,
  hungry: bool
}

#[repr(C)]
struct Dragon {
  foo:Foo,
  lairs: i32
}

trait IsFoo {
  fn foo(&mut self) -> &mut Foo; 
  fn as_foo<T: IsFoo>(mut self) -> Foo where Self: Sized {
    let ptr:*const Foo = self.foo() as *mut Foo;
    {
        self.foo().dispose = Some(Box::new(|&:foo:Foo| {
          println!("Incoming release for id {}", foo.id);
          unsafe {
            let tmp = &foo as *const Foo as *const T; // Avoid ICE. :(
            let mut instance:T = read::<T>(tmp);
            println!("Dropping instance with id: {}", instance.foo().id);
            drop(instance);
          }
        }));
    }
    unsafe { 
        let rtn = read(ptr);
        forget(self);
        return rtn;
    }
  }
}
impl IsFoo for Bat { fn foo(&mut self) -> &mut Foo { return &mut self.foo; } }
impl IsFoo for Goblin { fn foo(&mut self) -> &mut Foo { return &mut self.foo; } }
impl IsFoo for Dragon { fn foo(&mut self) -> &mut Foo { return &mut self.foo; } }

// Test drops work
impl Drop for Bat { fn drop(&mut self) { println!("Dropped a Bat"); } }
impl Drop for Goblin { fn drop(&mut self) { println!("Dropped a Goblin....!"); } }
impl Drop for Dragon { fn drop(&mut self) { println!("Dropped a Dragon"); } }

fn main() {
  {
    // Generic collection
    let monsters:Vec<Foo> = vec!(
      Bat { foo: Foo::new(1), hungry: true }.as_foo::<Bat>(),
      Goblin { foo: Foo::new(2), angry: true, friends: 100 }.as_foo::<Goblin>(),
      Dragon { foo: Foo::new(3), lairs: 33 }.as_foo::<Dragon>()
    );

    println!("Vector exists without dropping contents");

    // Cleanup
    for m in monsters.into_iter() { 
      println!("Dropping an instance: {}", m.id);
      m.release(); 
    }
  }
}

这里的输出是:

Vector exists without dropping contents
Dropping an instance: 1
Incoming release for id 1
Dropping instance with id: 1
Discarding a foo with dispose of false
Dropping an instance: 2
Incoming release for id 2
Dropping instance with id: 2
Discarding a foo with dispose of false
Dropping an instance: 3
Incoming release for id 3
Dropping instance with id: 3
Discarding a foo with dispose of false

即。虽然我将引用*mut Foo转换为封闭中的T,但GoblinDragon&amp; Bat无法运行。

如果稍微调整代码,例如http://is.gd/mRzj8H,您可以运行这些(或其中一些):

Vector exists without dropping contents
Dropping an instance: 1
Dropped a Bat
Dropping an instance: 2 <--- WTF, the others work but this one doesn't?
Dropping an instance: 3
Dropped a Dragon

我在这里看到某种种族情况的文物,还是更复杂的东西?

2 个答案:

答案 0 :(得分:2)

尝试使用不同的优化级别编译程序。 (在Rust围栏中有一个下拉列表;默认值为-O2。)您将看到输出不一致。在这种情况下,您将调用未定义的行为。

当您向Vec添加项目时,您可以创建包含Bat的{​​{1}}对象,然后只在Foo中添加Foo Vec不再存在。 Bat取值IsFoo::as_foo,这意味着它需要拥有它。 BatBat结束时被有效删除,但您通过调用as_foo来抑制掉落胶水。

在你的&#34;处置&#34; lambda,你试图通过将forget的指针转换为指向Bat的指针来获得Foo。这是未定义的行为,因为该向量仅包含Bat,而不是整个Foo。请记住,离开BatBat已被销毁。

使用IsFoo::as_foo-O2,该计划可能根本不会创建-O3,而Vec,{{ 1}}和Bat值在堆栈上仍然完好无损。但是,Dragon没有Goblin,而其他两个都有,所以这可能就是为什么析构函数不会只运行那个。

答案 1 :(得分:0)

在Rust 1.0 Drop类型不支持#[repr(C)]

这是因为Drop为结构添加了一个隐藏标志,并且只有在设置了该标志时才运行析构函数。

这是一种已知的语言疣,将来可能会通过跟踪带外标记来改变。

#[unsafe_no_drop_flag]删除Drop标志,但是Rust不保证析构函数被调用多少次(你的析构函数必须能够安全地运行已经存在的对象)破坏)。