这似乎可行,但它确实需要使用一个小的 unsafe 块,因此您应该在 Miri 和 Valgrind 等普通工具下进行测试。这里的主要假设1 是c_void 不能正常构造。 #[repr(transparent)] 用于确保FooBorrowed 新类型与c_void 具有相同的内存布局。一切都应该以“只是一个指针”结束:
use std::{ffi::c_void, mem, ops::Deref};
#[repr(transparent)]
struct FooBorrowed(c_void);
struct FooOwned(*mut c_void);
fn fake_foo_new(v: u8) -> *mut c_void {
println!("C new called");
Box::into_raw(Box::new(v)) as *mut c_void
}
fn fake_foo_free(p: *mut c_void) {
println!("C free called");
let p = p as *mut u8;
if !p.is_null() {
unsafe { Box::from_raw(p) };
}
}
fn fake_foo_value(p: *const c_void) -> u8 {
println!("C value called");
let p = p as *const u8;
unsafe {
p.as_ref().map_or(255, |p| *p)
}
}
impl FooBorrowed {
fn value(&self) -> u8 {
fake_foo_value(&self.0)
}
}
impl FooOwned {
fn new(v: u8) -> FooOwned {
FooOwned(fake_foo_new(v))
}
}
impl Deref for FooOwned {
type Target = FooBorrowed;
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute(self.0) }
}
}
impl Drop for FooOwned {
fn drop(&mut self) {
fake_foo_free(self.0)
}
}
fn use_it(foo: &FooBorrowed) {
println!("{}", foo.value())
}
fn main() {
let f = FooOwned::new(42);
use_it(&f);
}
如果 C 库真的给你一个指针,你需要做更多unsafe:
fn fake_foo_borrowed() -> *const c_void {
println!("C borrow called");
static VALUE_OWNED_ELSEWHERE: u8 = 99;
&VALUE_OWNED_ELSEWHERE as *const u8 as *const c_void
}
impl FooBorrowed {
unsafe fn new<'a>(p: *const c_void) -> &'a FooBorrowed {
mem::transmute(p)
}
}
fn main() {
let f2 = unsafe { FooBorrowed::new(fake_foo_borrowed()) };
use_it(f2);
}
如您所见,FooBorrowed::new 返回一个具有不受限制的生命周期的引用;这是相当危险的。在许多情况下,您可以构建更小的范围并使用提供生命周期的东西:
impl FooBorrowed {
unsafe fn new<'a>(p: &'a *const c_void) -> &'a FooBorrowed {
mem::transmute(*p)
}
}
fn main() {
let p = fake_foo_borrowed();
let f2 = unsafe { FooBorrowed::new(&p) };
use_it(f2);
}
这会阻止您在指针变量有效时使用引用,不保证是真正的生命周期,但在许多情况下“足够接近”。太短不能太长更重要!
1 — 在未来的 Rust 版本中,您应该使用外部类型来创建有保证的不透明类型:
extern "C" {
type my_opaque_t;
}