使用 Piston image
crate,我可以通过输入 a 来编写图像Vec<u8>
,但我的实际数据是Vec<Rgb<u8>>
(因为这更容易处理,并且我想动态增长它)。
我怎样才能转换Vec<Rgb<u8>>
为Vec<u8>
?Rgb<u8>
是真的[u8; 3]
。这必须是unsafe
转换吗?
使用 Piston image
crate,我可以通过输入 a 来编写图像Vec<u8>
,但我的实际数据是Vec<Rgb<u8>>
(因为这更容易处理,并且我想动态增长它)。
我怎样才能转换Vec<Rgb<u8>>
为Vec<u8>
?Rgb<u8>
是真的[u8; 3]
。这必须是unsafe
转换吗?
答案取决于您是否可以复制数据。如果复制对您来说不是问题,您可以执行以下操作:
let img: Vec<Rgb<u8>> = ...;
let buf: Vec<u8> = img.iter().flat_map(|rgb| rgb.data.iter()).cloned().collect();
但是,如果您想在不复制的情况下执行转换,我们首先需要确保您的源类型和目标类型实际上具有相同的内存布局。Rust 很少保证结构的内存布局。它目前甚至不保证具有单个成员的结构具有与成员本身相同的内存布局。
在这种特殊情况下,Rust 内存布局并不相关,因为Rgb
它被定义为
#[repr(C)]
pub struct Rgb<T: Primitive> {
pub data: [T; 3],
}
该#[repr(C)]
属性指定结构的内存布局应与等效的 C 结构相同。C 内存布局在 C 标准中没有完全指定,但根据不安全代码指南,有一些适用于“大多数”平台的规则:
- 字段顺序被保留。
- 第一个字段从偏移量 0 开始。
- 假设结构未打包,则每个字段的偏移量与该字段类型的 ABI 强制对齐方式对齐,可能会创建未使用的填充位。
- 结构的总大小向上舍入到其整体对齐。
正如评论中所指出的,C 标准理论上允许在结构的末尾进行额外的填充。但是,活塞图像库本身假设通道数据的片段与Rgb
struct具有相同的内存布局,因此,如果您处于此假设不成立的平台上,则无论如何都不会下注(我找不到任何证明存在这样一个平台的证据)。
Rust 确实保证数组、切片和向量是密集打包的,并且结构和数组的对齐等于它们元素的最大对齐。再加上Rgb
我上面引用的规则指定的布局的假设,这保证了Rgb<u8>
确实在内存中布局为三个连续字节,并且Vec<Rgb<u8>>
确实是一个连续的、密集打包的 RGB 值缓冲区,所以我们的转换是安全的。我们仍然需要使用不安全的代码来编写它:
let p = img.as_mut_ptr();
let len = img.len() * mem::size_of::<Rgb<u8>>();
let cap = img.capacity() * mem::size_of::<Rgb<u8>>();
mem::forget(img);
let buf: Vec<u8> = unsafe { Vec::from_raw_parts(p as *mut u8, len, cap) };
如果要防止后面有额外的padding的情况Rgb
,可以检查是否size_of::<Rgb<u8>>()
确实是3。如果是,可以使用unsafe non-copying版本,否则必须使用上面的第一个版本。
您选择Vec<Rgb<u8>>
存储格式是因为它更容易处理并且您希望它动态增长。但正如您所注意到的,不能保证其存储与 a 的兼容性Vec<u8>
,也不能安全转换。
为什么不把问题换一种方式,为 a 建立一个方便的门面Vec<u8>
呢?
type Rgb = [u8; 3];
#[derive(Debug)]
struct Img(Vec<u8>);
impl Img {
fn new() -> Img {
Img(Vec::new())
}
fn push(&mut self, rgb: &Rgb) {
self.0.push(rgb[0]);
self.0.push(rgb[1]);
self.0.push(rgb[2]);
}
// other convenient methods
}
fn main() {
let mut img = Img::new();
let rgb : Rgb = [1, 2, 3];
img.push(&rgb);
img.push(&rgb);
println!("{:?}", img);
}