What is the correct way to write Vec <u16> `contents to a file?
I am having trouble writing the contents of Vec<u16> to a file:
use std::fs::File; use std::io::{Write, BufWriter}; use std::mem; #[derive(Debug, Copy, Clone, PartialEq)] pub enum ImageFormat { GrayScale, Rgb32, } #[derive(Debug, Copy, Clone, PartialEq)] pub struct ImageHeader { pub width: usize, pub height: usize, pub format: ImageFormat, } pub struct Image { pub header: ImageHeader, pub data: Vec<u16>, } fn write_to_file(path: &str, img: &Image) -> std::io::Result<()> { let f = try!(File::create(path)); let mut bw = BufWriter::new(f); let slice = &img.data[..]; println!("before length: {}", slice.len()); let sl: &[u8]; unsafe { sl = mem::transmute::<&[u16], &[u8]>(slice); } println!("after length: {}", sl.len()); try!(bw.write_all(sl)); return Ok(()); } fn main() {} Since write_all() asks for a &[u8] , I am doing an unsafe conversion of &[u16] to &[u8] . Since the conversion does not change, the slice length ( slice.len() and sl.len() have the same value), only half of the image data is output to the file.
It would be better if I do not need any unsafe conversion or copying.
To do this directly, you want to use std::slice::from_raw_parts() :
use std::slice; use std::mem; fn main() { let slice_u16: &[u16] = &*vec![1, 2, 3, 4, 5, 6]; println!("u16s: {:?}", slice_u16); let slice_u8: &[u8] = unsafe { slice::from_raw_parts( slice_u16.as_ptr() as *const u8, slice_u16.len() * mem::size_of::<u16>(), ) }; println!("u8s: {:?}", slice_u8); } It requires unsafe , because from_raw_parts() cannot guarantee that you can only pass a valid pointer to it, and also create slices with arbitrary lifetimes.
However, this approach is not only potentially dangerous, but also not tolerated. When you work with integers greater than one byte, content problems immediately arise. If you write the file this way on the x86 machine, then you will read the garbage on the ARM machine. The correct way is to use libraries such as byteorder that allow you to explicitly specify an entity:
extern crate byteorder; use byteorder::{WriteBytesExt, LittleEndian}; fn main() { let slice_u16: &[u16] = &*vec![1, 2, 3, 4, 5, 6]; println!("u16s: {:?}", slice_u16); let mut result: Vec<u8> = Vec::new(); for &n in slice_u16 { let _ = result.write_u16::<LittleEndian>(n); } println!("u8s: {:?}", result); } Note that I used Vec<u8> , but it implements Write , and write_u16() and other methods from the WriteBytesExt type are defined on any Write , so you can use these methods directly on BufWriter , for example.
Although this may be slightly less efficient than reinterpreting part of the memory, it is safe and portable.
I recommend using existing serialization libraries like serde and bincode :
extern crate bincode; extern crate serde; #[macro_use] extern crate serde_derive; use std::error::Error; #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] pub enum ImageFormat { GrayScale, Rgb32, } #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] pub struct ImageHeader { pub width: usize, pub height: usize, pub format: ImageFormat, } #[derive(Serialize, Deserialize)] pub struct Image { pub header: ImageHeader, pub data: Vec<u16>, } impl Image { fn save_to_disk(&self, path: &str) -> Result<(), Box<Error>> { use std::fs::File; use std::io::Write; let bytes: Vec<u8> = try!(bincode::serialize(self, bincode::Infinite)); let mut file = try!(File::create(path)); file.write_all(&bytes).map_err(|e| e.into()) } } fn main() { let image = Image { header: ImageHeader { width: 2, height: 2, format: ImageFormat::GrayScale, }, data: vec![0, 0, 0, 0], }; match image.save_to_disk("image") { Ok(_) => println!("Saved image to disk"), Err(e) => println!("Something went wrong: {:?}", e.description()), } }