2015-06-15 10 views
6

Mam problemy z pisania Vec<u16> zawartość do pliku:Jaki jest prawidłowy sposób zapisu treści `Vec <u16>` do pliku?

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() {} 

Od write_all() prosi o &[u8], robię niebezpiecznego konwersję &[u16] do &[u8]. Ponieważ konwersja nie zmienia długości plasterka (slice.len() i sl.len() mają te same wartości), tylko połowa danych obrazu jest wysyłana do pliku.

Byłoby lepiej, gdybym nie potrzebował żadnej niebezpiecznej konwersji lub kopiowania.

Odpowiedz

7

Aby to zrobić bezpośrednio, czego chcesz używać 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); 
} 

To wymaga unsafe ponieważ from_raw_parts() nie może zagwarantować, że można jedynie zdać ważny wskaźnik do niego, a także może tworzyć plastry z dowolnym czasem życia.

Jednak takie podejście jest nie tylko potencjalnie niebezpieczne, ale również nie jest przenośne. Gdy pracujesz z liczbami całkowitymi większymi niż jeden bajt, natychmiast pojawiają się problemy z endianowością. Jeśli napiszesz plik w ten sposób na komputerze z procesorem x86, wówczas będziesz czytał śmieci na maszynie ARM. Właściwym sposobem jest użycie biblioteki jak byteorder które pozwalają określić kolejność bajtów wyraźnie:

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); 
} 

Zauważ, że mam używany Vec<u8> tutaj, ale realizuje Write i write_u16() i inne metody z WriteBytesExt cechy są zdefiniowane na dowolny Write, więc możesz użyć tych metod bezpośrednio na przykład na BufWriter.

Może to być nieco mniej wydajne niż reinterpretowanie pamięci, ale jest bezpieczne i przenośne.

+0

Dzięki! Próbowałem twojej metody używania 'slice :: from_raw_parts()' i działało idealnie. – rillomas

5

Polecam wykorzystanie istniejących bibliotek dla serializacji takich jak serde i 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()), 
    } 
} 
+1

Dzięki! Próbowałem również twojej metody używania bincode + rustc-serialize, i osobiście wolę tę metodę bardziej niż wyrzucanie surowych danych do pliku (bez "niebezpiecznych", nie trzeba się martwić o endianness). Przyjmuję odpowiedź Vladimira, ponieważ bardziej dotyczy tematu, ale jak powiedziałeś, użycie serializacji jest prawdopodobnie lepszym rozwiązaniem. – rillomas

Powiązane problemy