【问题标题】:What is the better way to wrap a FFI struct that owns or borrows data?包装拥有或借用数据的 FFI 结构的更好方法是什么?
【发布时间】:2018-10-10 23:29:07
【问题描述】:

我有一个可以从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,拥有和借用。 (如字符串/&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.

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

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

我的第一个想法是有两个生命周期'a 和'b,其中'a 代表自我的范围,'b 代表返回对象的范围。 对于参考图像,我想强制执行 'a == '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 中实现操作:

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

或者,搜索“rust 生命周期作为类型关联”给了我这个 RFC: https://github.com/rust-lang/rfcs/pull/1598 (我正在阅读。这适用于我的情况吗?)

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

【问题讨论】:

  • 我不确定我的阅读是否正确,但您可能想看看owning_ref
  • from_vec 绝对不安全。 Vec 将在函数返回时被删除,从而使指针无效。您至少需要mem::forget(vec) 才能使其工作
  • 我认为您的 resize 函数不存在,因为您不保留有关指针来源的任何信息。您希望函数在拥有self 时返回拥有的Image,并在借用self 时返回借用的self,但您没有跟踪任何区分它们的方法。 ffi::resize 是做什么的?
  • 相反,我认为这都是关于管理 FFI 对象的。如果调整大小的图像可能引用输入像素数据fn resize(..) -&gt; OwnedImage 怎么能安全?
  • 另外,我想你没有意识到你调用mem::forgetVec 永远无法被释放。 Rust 的分配器必须释放数据,除非您保存向量的原始容量并调用Vec::from_raw_parts 来重构它,否则Vec 将永远泄漏。将它传递给 C 的 freeg_object_unref 可能什么都不做,或者可能损坏数据,但它不会释放切片。

标签: rust lifetime


【解决方案1】:

结构

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 时,我需要充分了解生命周期省略规则,否则它可能会默默地允许不良用法。

【讨论】:

  • drop(b); 是不需要的——值被丢弃在块的末尾。 let _img: Image 不需要类型,是推断出来的。
猜你喜欢
  • 1970-01-01
  • 2012-02-17
  • 2020-07-11
  • 2012-02-03
  • 1970-01-01
  • 2011-05-27
  • 1970-01-01
  • 2021-09-12
  • 2014-05-03
相关资源
最近更新 更多