为什么Drop glue有时不运行?
Why does Drop glue sometimes not run?
我在其他情况下也遇到过这种情况,但不会重复发生。
基本上,有时当您有一个 *mut Foo
,然后使用 ptr::read()
将其变成 Foo
,有时您的滴胶似乎并不 运行 .
这是一个可重复的例子,运行在游戏围栏上没有使用 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
,但 Goblin
、Dragon
和 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
我在这里看到的是某种竞争条件的伪影,还是更复杂的东西?
尝试用不同的优化级别编译您的程序。 (在 Rust playpen 上有一个下拉菜单;默认值为 -O2
。)您会看到输出不一致。在这种情况下,您正在调用未定义的行为。
当您向 Vec
添加项目时,您会创建包含 Foo
的 Bat
等对象,然后您只需在 [=11] 中添加 Foo
=]. Bat
不再存在。 IsFoo::as_foo
按值获取 Bat
,这意味着它取得了它的所有权。 Bat
在 as_foo
的末尾有效地掉落,但您通过调用 forget
.
抑制了掉落粘连
在您的 "dispose" lambda 中,您尝试通过将指向 Foo
的指针转换为指向 Bat
的指针来获得 Bat
。这是 未定义的行为,因为向量只包含 Foo
,而不是整个 Bat
。请记住,Bat
在离开 IsFoo::as_foo
时被销毁了。
对于 -O2
和 -O3
,程序可能根本没有创建 Vec
,而 Bat
、Dragon
和Goblin
值在堆栈中仍然完好无损。但是,Goblin
没有 #[repr(C)]
,而其他两个有,所以这可能就是为什么析构函数没有 运行 的原因。
在 Rust 1.0 中 Drop
不支持 #[repr(C)]
类型。
这是因为 Drop
向结构添加了一个隐藏标志,并且 运行 只有在设置了该标志时才会析构函数。
这是该语言中的已知缺点,将来可能会通过跟踪带外丢弃标志来更改它。
有 #[unsafe_no_drop_flag]
删除了 Drop
标志,但是 Rust 不保证析构函数将被调用多少次(你的析构函数必须 运行 能够安全地一个已经被销毁的对象)。
我在其他情况下也遇到过这种情况,但不会重复发生。
基本上,有时当您有一个 *mut Foo
,然后使用 ptr::read()
将其变成 Foo
,有时您的滴胶似乎并不 运行 .
这是一个可重复的例子,运行在游戏围栏上没有使用 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
,但 Goblin
、Dragon
和 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
我在这里看到的是某种竞争条件的伪影,还是更复杂的东西?
尝试用不同的优化级别编译您的程序。 (在 Rust playpen 上有一个下拉菜单;默认值为 -O2
。)您会看到输出不一致。在这种情况下,您正在调用未定义的行为。
当您向 Vec
添加项目时,您会创建包含 Foo
的 Bat
等对象,然后您只需在 [=11] 中添加 Foo
=]. Bat
不再存在。 IsFoo::as_foo
按值获取 Bat
,这意味着它取得了它的所有权。 Bat
在 as_foo
的末尾有效地掉落,但您通过调用 forget
.
在您的 "dispose" lambda 中,您尝试通过将指向 Foo
的指针转换为指向 Bat
的指针来获得 Bat
。这是 未定义的行为,因为向量只包含 Foo
,而不是整个 Bat
。请记住,Bat
在离开 IsFoo::as_foo
时被销毁了。
对于 -O2
和 -O3
,程序可能根本没有创建 Vec
,而 Bat
、Dragon
和Goblin
值在堆栈中仍然完好无损。但是,Goblin
没有 #[repr(C)]
,而其他两个有,所以这可能就是为什么析构函数没有 运行 的原因。
在 Rust 1.0 中 Drop
不支持 #[repr(C)]
类型。
这是因为 Drop
向结构添加了一个隐藏标志,并且 运行 只有在设置了该标志时才会析构函数。
这是该语言中的已知缺点,将来可能会通过跟踪带外丢弃标志来更改它。
有 #[unsafe_no_drop_flag]
删除了 Drop
标志,但是 Rust 不保证析构函数将被调用多少次(你的析构函数必须 运行 能够安全地一个已经被销毁的对象)。