如何处理可以拥有或借用的FFI未分类型?

时间:2017-03-05 20:49:05

标签: rust ffi lifetime

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

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

这是可能的,它将如何完成?

1 个答案:

答案 0 :(得分:2)

这似乎有效,但确实需要使用小unsafe块,因此您应该使用Miri和Valgrind等常规工具进行测试。这里 1 的主要假设是c_void无法正常构造。 #[repr(transparent)]用于确保FooBorrowed newtype与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类型来创建保证的opaque类型:

extern "C" {
    type my_opaque_t;
}