如何在Rust中存储一个指向结构体的void*引用?

4

我正在与一些使用标准 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 值,在移动它时不改变其地址?
1个回答

6
您可以通过堆分配对象来防止值更改地址。这样做会导致需要解引用才能访问它,但它将是固定的:
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装箱可能更安全,存储对盒装单元的不可变指针,然后通过将指针转换为&RefCell并在引用上调用borrow_mut()来将其转换回&mut RealThing。如果您犯了错误,至少Rust会通过抛出panic来发出警告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接