Какой лучший способ обернуть структуру FFI, которая владеет или заимствует данные? - PullRequest
0 голосов
/ 30 апреля 2018

У меня есть структура Image, которая может быть построена из Vec<u8> или &[u8]. Он представляет объект изображения в библиотеке 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 имеет только необработанный указатель и не заимствует, поэтому компилятор не ограничивает время жизни при выводе операции изменения размера.

с вектором, это нормально:

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 / & 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

Или разделить на две структуры, OwnedImage и ScopedImage и реализовать операции в качестве:

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

Или, поиск «время жизни ржавчины как ассоциация типа» дает мне RFC: https://github.com/rust-lang/rfcs/pull/1598 (Я читаю это. Применимо ли это к моему делу?)

Впервые я пишу серьезный код на Rust со сложными обобщениями и временами жизни. На самом деле я не спрашиваю, что лучше (хотя мне интересно их плюсы / минусы, а какие идиоматические), я просто не знаю, какой из этих вариантов возможен.

1 Ответ

0 голосов
/ 05 мая 2018

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 я должен быть полностью осведомлен о правилах жизненного выбора, в противном случае он может молча допускать неправильное использование.

...