如何处理可以拥有或借用的 FFI 未确定大小的类型?

How do I handle an FFI unsized type that could be owned or borrowed?

c_strange_t 是一种不透明的 C 类型,只能在指针后面看到。包装这种类型时,有时我们有责任使用 c_free_strange_t(*c_strange_t) 释放内存,而有时我们不负责释放数据,我们只负责准确控制生命周期。

如果这种类型可以映射到 Rust 中的 2 种类型,这两种类型的工作方式与 strString 类似,其中有 impl Deref<Target=str> for String,那将是符合人体工程学的。借用的类型需要标记为仅在引用后有效。

这可能吗,如何实现?

这似乎可行,但它确实需要使用一个小的 unsafe 块,因此您应该在 Miri 和 Valgrind 等常规工具下进行测试。这里做的首要假设1是不能正常构造c_void#[repr(transparent)] 用于确保 FooBorrowed 新类型与 c_void 具有相同的内存布局。一切都应该以 "just a pointer":

结尾
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 returns 一个无限制生命周期的引用;这很危险。在很多情况下,你可以构造一个更小的范围并使用提供生命周期的东西:

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);
}

这会阻止您在指针变量有效时使用引用,这保证是真正的生命周期,但在许多情况下是"close enough" .太短不要太长更重要!


1 — 在 Rust 的未来版本中,您应该使用外部类型来创建有保证的不透明类型:

extern "C" {
    type my_opaque_t;
}