包装拥有或借用数据的FFI结构的更好方法是什么?

时间:2018-04-30 20:32:10

标签: rust lifetime

我有一个可以从Vec<u8>&[u8]构建的Image结构。 它表示C库中的图像对象(ffi模块)。

struct Image { ptr: *mut c_void };

impl Image {
    fn from_vec(vec: Vec<u8>) -> Image {
        // transfer ownership to gobject system
        let ptr = unsafe {
            ffi::new(
                vec.as_ptr() as *const c_void,
                vec.len(),
                ..
            )
        };
        std::mem::forget(vec);
        Image { ptr }
    }
    fn from_ref(data: &[u8]) -> Image {
        // gobject doesn't free data on Drop
        let ptr = unsafe {
            ffi::new_ref(
                data.as_ptr() as *const c_void,
                data.len(),
                ..
            )
        };
        Image { ptr }
    }

    fn resize(&self, ..) -> Image {
        let new_ptr = unsafe { ffi::resize(self.ptr) };
        Image { new_ptr }
    }
}

impl Drop for Image {
    fn drop(&mut self) {
        unsafe {
            ffi::g_object_unref(self.ptr as *mut c_void);
        }
    }
}

Image结构只有原始指针而且没有借位,因此编译器不会对resize操作的输出设置生命周期约束。

用矢量

,这没关系:

let img1 = Image::from_vec(pixels); // consume pixels
let img2 = img1.resize(..);
return img2;
// when img2 is released, gobject system will release pixels as well

然而,有了参考,这是一个问题:

let pixels = Vec::new(..);
let img1 = Image::from_ref(&pixels);
let img2 = img1.resize(..)
return img2;
// danger: img2's gobject has a raw pointer to pixels

编译器没有抱怨,但为了防止这种情况,我希望编译器通过添加生命周期来抱怨。

我知道一个工作解决方案是拥有和借用两个版本的Image。 (如String /&amp; str)。但是,我不想重复相同的代码,这些代码仅在返回类型中有所不同:

impl OwnedImage {
    fn resize(..) -> OwnedImage {
        let new_ptr = unsafe { ffi::resize(self.ptr) };
        OwnedImage{ptr:new_ptr}
    }
}

// ScopedImage needs a PhantomData.
struct ScopedImage<'a> { ptr: *mut c_void, marker: PhantomData<&'a ()> }
impl<'a> ScopedImage<'a> {
    fn resize(..) -> ScopedImage<'a> {
        let new_ptr = unsafe { ffi::resize(self.ptr) };
        ScopedImage{ptr:new_ptr, PhantomData}
    }
}

let pixels = Vec::new(..);
let img1 = ScopedImage::from_ref(&pixels);
let img2 = img1.resize(..);
return img2; // error, as I intended.

与&amp; str / String不同,两种类型的区别仅在于编译器是否会在某些情况下抱怨。

我的问题是,是否可以将两种类型合并为一个带有生命周期参数的类型。

我的第一个想法是有两个生命周期&#39; a和&#b; b,其中&#39; a表示自己的范围,&b; b表示返回对象的范围。 对于参考图片,我想强制执行&#39; a ==&#39; b但我不确定如何实现。

    // for vec, 'a!='b. for ref, 'a=='b

    struct Image<'a, 'b> { ptr, ?? }

    // this type parameter relationship is
    //    enforced at the construction

    from_vec(..) -> Image<'a,'a>
    from_ref<'b> (&'a data) -> Image<'a,'b>

    resize<'b>(&self, ..) -> Image<'b>

或有一生:

    type R = (Image:'a  or Image:'b);
    resize(&self, ..) -> R // R: return type, decided on construction

或者拆分为两个结构OwnedImageScopedImage并在特征中实现操作:

    trait ImageTrait<'a> {
        type OutputImage: 'a;

        fn resize(..) -> Self::OutputImage {
            ..
        }
    }

    impl<'a> ImageTrait<'a> for OwnedImage {
        type OutputImage = OwnedImage;
    }

    impl<'a, 'b> ImageTrait<'b> for ScopedImage {
        type OutputImage = ScopedImage;
    }

或者,将生锈期限作为类型关联进行搜索&#39;给我这个RFC: https://github.com/rust-lang/rfcs/pull/1598 (我正在读这个。这适用于我的情况吗?)

这是我第一次编写具有复杂泛型和生命周期的严重Rust代码。 我实际上并不是在问哪个更好(虽然我不知道他们的优点/缺点以及哪些是惯用的),我甚至不知道哪些选项是可能的。

1 个答案:

答案 0 :(得分:2)

<强> STRUCT

pub struct Image<'a> {
    pub c: *mut ffi::Image,
    marker: PhantomData<&'a()>,
}

取消分配回调

pub unsafe extern "C" fn cleanup(ptr: *mut ffi::Image, user_data: *mut c_void) {
    let b: Box<Box<[u8]>> = Box::from_raw(user_data as *mut Box<[u8]>);
    println!(" >>>> releasing slice of len {}", b.len());
    drop(b);
}

参考构造函数

impl<'a> Image<'a> {
    pub fn from_memory_reference(buf: &'a [u8] /* ... */) -> Result<Image, Box<Error>> {
        let c = unsafe {
            ffi::image_new_from_memory(
                buf.as_ptr() as *const c_void,
                // ...
            )
        };

        Ok(Image {
            ptr: c,
            PhantomData,
        })
    }
}

拥有的构造函数

解决方案是让参数'a保持不足。

impl<'a> Image<'a> {
    pub fn from_memory(buf: Vec<u8> /* ... */) -> Result<Image<'a>, Box<Error>> {
        let b: Box<[_]> = buf.into_boxed_slice();
        let c = unsafe {
            ffi::image_new_from_memory(
                b.as_ptr() as *const c_void,
                // ...
            )
        };

        let bb: Box<Box<_>> = Box::new(b);
        let raw: *mut c_void = Box::into_raw(bb) as *mut c_void;

        unsafe {
            let callback: unsafe extern "C" fn() = ::std::mem::transmute(cleanup as *const ());

            ffi::g_signal_connect_data(
                c as *mut c_void,
                "close_signal\0".as_ptr() as *const c_char,
                Some(callback),
                raw,
                None,
                ffi::GConnectFlags::G_CONNECT_AFTER,
            );
        };

        Ok(Image {
            ptr: c,
            PhantomData,
        })
    }
}

<强>操作

fn resize(&self, scale: f64) -> Result<Image, Box<Error>> {
    // ...
}

参考测试

let _img: Image = {
    let pixels = vec![0; 256 * 256 * 3];
    Image::from_memory_reference(&pixels, /* ... */).unwrap()
    //~^ ERROR `pixels` does not live long enough
};

拥有测试

let _img: Image = {
    let pixels = vec![0; 256 * 256 * 3];
    Image::from_memory(pixels, /* ... */).unwrap()
}; // Ok

缺点是,在编写API时,我需要完全了解生命周期的省略规则,否则它可能会默默地允许不良用法。