如何将 Vec<Rgb<u8>> 转换为 Vec<u8>

How to convert Vec<Rgb<u8>> to Vec<u8>

使用 Piston image crate,我可以通过输入一个 Vec<u8> 来写入图像,但我的实际数据是 Vec<Rgb<u8>>(因为这更容易处理,我想动态地增长它)。

如何将 Vec<Rgb<u8>> 转换为 Vec<u8>Rgb<u8> 真的是 [u8; 3]。这是否必须是 unsafe 转换?

您选择 Vec<Rgb<u8>> 存储格式是因为它更容易处理并且您希望它动态增长。但正如您所注意到的,无法保证其存储与 Vec<u8> 的兼容性,也无法进行安全转换。

为什么不换个角度解决问题,为 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);
}

答案取决于您是否可以复制数据。如果复制对你来说不是问题,你可以这样做:

let img: Vec<Rgb<u8>> = ...;
let buf: Vec<u8> = img.iter().flat_map(|rgb| rgb.data.iter()).cloned().collect();

但是,如果您想在不复制的情况下执行转换,我们首先需要确保您的源类型和目标类型实际上具有相同的内存布局。 Rust 很少保证结构的内存布局。它目前甚至不保证具有单个成员的结构与成员本身具有相同的内存布局。

在这种特殊情况下,Rust 内存布局并不相关,因为 Rgb is defined as

#[repr(C)]
pub struct Rgb<T: Primitive> {
    pub data: [T; 3],
}

#[repr(C)] 属性指定结构的内存布局应与等效的 C 结构相同。 C 内存布局在 C 标准中没有完全指定,但根据 unsafe code guidelines,有一些适用于 "most" 平台的规则:

  • Field order is preserved.
  • The first field begins at offset 0.
  • Assuming the struct is not packed, each field's offset is aligned to the ABI-mandated alignment for that field's type, possibly creating unused padding bits.
  • The total size of the struct is rounded up to its overall alignment.

正如评论中所指出的,C 标准理论上允许在结构的末尾进行额外的填充。但是,Piston 图像库本身 makes the assumption that a slice of channel data has the same memory layout as the 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) };

如果要防止Rgb末尾有额外填充的情况,可以检查size_of::<Rgb<u8>>()是否确实是3,如果是,可以使用unsafe非复制版,否则必须使用上面的第一个版本。