【发布时间】: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
或者拆分成两个结构,OwnedImage 和 ScopedImage,并在一个 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(..) -> OwnedImage怎么能安全? -
另外,我想你没有意识到你调用
mem::forget的Vec永远无法被释放。 Rust 的分配器必须释放数据,除非您保存向量的原始容量并调用Vec::from_raw_parts来重构它,否则Vec将永远泄漏。将它传递给 C 的free或g_object_unref可能什么都不做,或者可能损坏数据,但它不会释放切片。