I have an Image struct that can be constructed from a Vec<u8>
or a &[u8]
.
It represents an image object in C library (ffi module).
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);
}
}
}
The Image struct has only raw pointer and no borrow, so the compiler puts no lifetime constraint on the output of resize operation.
with a vector, this is ok:
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
However, with a reference, this is a problem:
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
The compiler doesn't complain, but to prevent this case, I want the compiler to complain by adding a lifetime.
A working solution I know is to have two versions of Image, owned and borrowed. (like String/&str). However I don't want to repeat the same code which differs only in return type:
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.
Unlike &str/String, two types differ only in whether the compiler complains or not for some cases.
My question is if it is possible to incorporate two types into one with lifetime parameter.
My first idea was having two lifetimes 'a and 'b, where 'a represents self's scope and 'b represents the scope of returned objects. For reference image, I want to enforce 'a == 'b but I am not sure how to achieve that.
// 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>
Or with one lifetime:
type R = (Image:'a or Image:'b);
resize(&self, ..) -> R // R: return type, decided on construction
Or split into two structs, OwnedImage
and ScopedImage
and implement operations in a 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;
}
Or, searching 'rust lifetime as type association' gives me this RFC: https://github.com/rust-lang/rfcs/pull/1598 (I am reading this. Is this applicable to my case?)
This is the first time I am writing a serious Rust code with complex generics and lifetimes. I am not actually asking which is better (though I wonder their pros/cons and which is idiomatic), I just don't even know which of these options are possible.
from_vec
is definitely not safe. TheVec
will be dropped when the function returns, invalidating the pointer. You need to at leastmem::forget(vec)
to make it work – trentclresize
function can exist, because you don't retain any information about the provenance of your pointer. You want the function to return an ownedImage
whenself
is owned, and a borrowed one whenself
is borrowed, but you aren't keeping track of any way to distinguish them. What doesffi::resize
do? – trentclfn resize(..) -> OwnedImage
be safe, if the resized image may reference the input pixel data? – trentclVec
you calledmem::forget
on can never be freed. Rust's allocator has to free the data and, unless you save the vector's original capacity and callVec::from_raw_parts
to reconstitute it, thatVec
is leaked forever. Passing it to C'sfree
or tog_object_unref
may do nothing, or may corrupt data, but it won't free the slice. – trentcl