У меня есть структура 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 со сложными обобщениями и временами жизни.
На самом деле я не спрашиваю, что лучше (хотя мне интересно их плюсы / минусы, а какие идиоматические), я просто не знаю, какой из этих вариантов возможен.