Programmieren in Rust

Serialisierung

Inhaltsverzeichnis

  1. Serialisierung als Text
    1. Serde für JSON
  2. Serialisierung als Binärdaten
    1. Byte-Reihenfolge
    2. Serialisierung von Strukturen

Serialisierung als Binärdaten

Byte-Reihenfolge

Bei der Serialisierung einer Zahl tut sich das Problem auf, dass die Byte-Reihenfolge von Computer-System zu Computer-System unterschiedlich sein kann.

Die Maschine, die das Programm ausführt, stellt eine Zahl in ihrem Arbeitsspeicher in einer bestimmten Form dar. Führt man nun eine sogenannte Transmutation aus, wo der Typ der Zahl als ein Array von Bytes uminterpretiert wird ohne eine Konvertierung vorzunehmen, enthüllt dies die interne Darstellung. Sendet die Maschine die Daten an eine andere, kommt es infolge zu einer Fehlinterpretation, sofern die Byte-Reihenfolgen der Maschinen unterschiedlich sind.

Zur Schaffung einer festen Schnittstelle, die zwischen unterschiedlichen Systemen vermittelt, einigt man sich bei einem Format auf eine feste Byte-Reihenfolge, – entweder Little-Endian oder Big-Endian.

Zur Umwandlung in das entsprechende Format gibt es zwei Möglichkeiten:

  1. Bei Benutzung von Bit-Arithmetik bleibt die Darstellung im Arbeitsspeicher verborgen, womit das Problem entfällt.
  2. Die Byte-Reihenfolge der Maschine lässt sich zur Kompilierzeit oder zur Laufzeit ermitteln. Im Anschluss an die Transmutation findet ggf. eine Umkehrung der Reihenfolge statt.

Die Standardbibliothek stellt für die Umwandlung bereits Funktionen zur Verfügung, so dass sich niemand mit Transmutationen befassen muss. Die folgende Tabelle listet die zur Verfügung gestellten Funktionen auf.

SerialisierungDeserialisierung
Little-Endianto_le_bytesfrom_le_bytes
Big-Endianto_be_bytesfrom_be_bytes
Nativto_ne_bytesfrom_ne_bytes

Das nächste Programm serialisiert ein Array von Zahlen des Typs u32 und schreibt die daraus entstandenen Binärdaten in eine Datei. Die Funktion to_le_bytes wandelt die jeweilige Zahl dabei in eine Bytesequenz der Reihenfolge Little-Endian um.

fn serialize_u32(a: &[u32]) -> Vec<u8> {
    let mut acc: Vec<u8> = Vec::with_capacity(4*a.len());
    for x in a {
        acc.extend(&u32::to_le_bytes(*x));
    }
    acc
}

fn main() {
    let a: &[u32] = &[1, 2, 3, 4];
    let data = serialize_u32(a);
    std::fs::write("Datei.bin", &data).unwrap();
}

Betrachtet man Datei.bin nun im Hex-Editor, findet man vor:

01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00

Ein zweites Programm liest die Datei wieder ein und deserialisiert die Daten. Die Funktion from_le_bytes setzt dabei die jeweilige Zahl aus der im Little-Endian-Format vorliegenden Bytesequenz zusammen.

fn main() {
    let bytes = std::fs::read("Datei.bin").unwrap();

    for t in bytes.chunks_exact(4) {
        let value = u32::from_le_bytes([t[0], t[1], t[2], t[3]]);
        println!("{}", value);
    }
}