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

后端 未结 1 1915
我寻月下人不归
我寻月下人不归 2021-01-21 05:53

c_strange_t is an opaque C type that is only seen behind a pointer. When wrapping this type, there are times when it is our responsibility to free memory using

相关标签:
1条回答
  • 2021-01-21 06:30

    This appears to work, but it does require using a small unsafe block, so you should test under the normal tools like Miri and Valgrind. The primary assumption made here1 is that c_void cannot be constructed normally. #[repr(transparent)] is used to ensure that the FooBorrowed newtype has the same memory layout as a c_void. Everything should end up as "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);
    }
    

    If the C library actually hands you a pointer, you would need to do some more 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);
    }
    

    As you identified, FooBorrowed::new returns a reference with an unrestricted lifetime; this is pretty dangerous. In many cases, you can construct a smaller scope and use something that provides a lifetime:

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

    This prevents you from using the reference beyond when the pointer variable is valid, which is not guaranteed to be the true lifetime, but is "close enough" in many cases. It's more important to be too short and not too long!


    1 — In future versions of Rust, you should use extern types to create a guaranteed opaque type:

    extern "C" {
        type my_opaque_t;
    }
    
    0 讨论(0)
提交回复
热议问题