String and Byte Serialization

5.2. String and Byte Serialization#

5.2.1. Byte Serialization#

Basic File-IO operations are similar to those in other low-level languages like C or C++. The following code sample demonstrates how to read and write 4 bytes (the typical size of a u32 integer) to a file in Rust.

fn write_bytes_to_file(bytes: [u8; 4], filename: &str) -> Result<(), Error> {
    // Create a File; see Rust doc for std::fs::File
    let mut buffer = File::create(filename)?;
    // Write bytes to that file
    buffer.write_all(&bytes)?;
    Ok(())
}

fn read_bytes_from_file(filename: &str) -> [u8; 4] {
    let f = File::open(filename).expect("could not open file");
    let mut reader = BufReader::new(f);
    let mut buffer = Vec::new();

    // Read file into vector.
    reader.read_to_end(&mut buffer).expect("error while reading file");

    // Transform Vec (much preferred way of handling collection of values) into array (for this example)
    let array = vec_to_array(buffer);
    array
}

fn vec_to_array<T, const N: usize>(v: Vec<T>) -> [T; N] {
    v.try_into()
        .unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
}

The crux of serializing a data structure is to find a byte representation of the same. Different computer architectures, however, will interpret bits within a byte differently.

In particular, when serializing numbers, byte order matters. Recall the differences between Little and Big Endian representation - you can read more in the Wikipedia article on Endianness. Rust provides multiple byte conversion traits for numeric types, such as to_le_bytes and to_be_bytes for little and big endian, respectively. The corresponding from_le_bytes and from_be_bytes are used to convert bytes back to numbers.

5.2.2. String Serialization#

The following code sample demonstrates how to read and write a string to a file in Rust. Like before, all values are first serialized to bytes and then read or written to a file.

fn write_string_to_file(string: &str, filename: &str) {
    let f = File::create(filename).expect("error creating file");
    let mut buf = BufWriter::new(f);
    buf.write_all(string.as_bytes()).unwrap();
}

fn read_string_from_file(filename: &str) -> String {
    let mut data = String::new();
    let f = File::open(filename).expect("error while opening file");
    let mut br = BufReader::new(f);
    br.read_to_string(&mut data).unwrap();
    data
}

Similarly, most variables can be serialized to a string representation using the from_string() and to_string() methods for numeric types.