我正在与一些使用标准void* userdata
方法的C回调进行交互,以允许您存储对某些上下文(例如结构)的引用。如何在void*
中存储对Rust结构的引用并仍允许它移动?似乎Rust移动确实是移动,即此代码失败(如预期的那样):
struct Thing {
pointer_to_self: *mut Thing,
}
fn create_thing() -> Thing {
let mut a = Thing {
pointer_to_self: std::ptr::null_mut(),
};
a.pointer_to_self = &mut a as *mut _;
a
}
fn main() {
let mut b = create_thing();
assert_eq!(&mut b as *mut _, b.pointer_to_self);
}
有解决方法吗?我可以使用Rust值来移动它时不会改变地址吗?
答案 0 :(得分:4)
您可以通过堆分配对象来阻止值更改地址。这将需要取消引用它,但它将是静止的:
struct RealThing {
// ...
}
struct Thing {
// pointer could also be a field in RealThing, but it seems to
// make more sense to leave only the actual payload there
real_thing: Box<RealThing>,
pointer_to_real: *mut RealThing,
}
fn create_thing() -> Thing {
let mut a = Thing {
real_thing: Box::new(RealThing {}),
pointer_to_real: std::ptr::null_mut(),
};
a.pointer_to_real = a.real_thing.as_mut() as *mut _;
a
}
fn main() {
let mut b = create_thing();
assert_eq!(b.real_thing.as_mut() as *mut _, b.pointer_to_real);
}
请注意,如果您尝试使用在此期间已经移动或复制构造的对象的地址,则在C ++中会遇到相同的问题。
警告:实际上使用指针将导致未定义的行为,除非采取预防措施以防止存在对同一对象的多个可写引用。 UnsafeCell
文档说:
通常,将
&T
类型转换为&mut T
被视为未定义的行为。编译器根据&T
不可变别名或变异的知识进行优化,&mut T
是唯一的。
框RefCell<RealThing>
可能更安全,存储指向盒装单元格的不可变指针,并通过将指针转换为&mut RealThing
并调用&RefCell<RealThing>
将其转换回borrow_mut()
在参考上。如果你犯了一个错误,至少Rust会因恐慌而警告你。