Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Zero-Copy Deserialization

Access serialized data without deserializing it

rkyv-badge cat-encoding-badge

rkyv lays out serialized bytes so that the buffer itself is a valid, aligned in-memory representation of the data. Deriving Archive on Player generates an ArchivedPlayer type whose fields are archived counterparts such as ArchivedString and ArchivedVec.

rkyv::to_bytes serializes the value into an AlignedVec. rkyv::access validates the buffer and returns a &ArchivedPlayer that points straight into it, so reading fields requires no parsing, copying, or allocation. Archived integers are stored in a fixed byte order, so to_native converts them to native integers for arithmetic. When an owned value is needed, rkyv::deserialize converts the archive back into a Player.

use rkyv::rancor::Error;
use rkyv::{Archive, Deserialize, Serialize};

#[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
struct Player {
    name: String,
    level: u32,
    scores: Vec<u32>,
}

fn main() -> Result<(), Error> {
    let player = Player {
        name: "Ferris".to_string(),
        level: 42,
        scores: vec![100, 250, 75],
    };

    let bytes = rkyv::to_bytes::<Error>(&player)?;

    // Validate the buffer and view it as an `ArchivedPlayer` without copying.
    let archived = rkyv::access::<ArchivedPlayer, Error>(&bytes)?;
    println!("name:  {}", archived.name.as_str());
    println!("level: {}", archived.level);
    let total: u32 = archived.scores.iter().map(|s| s.to_native()).sum();
    println!("total: {total}");

    // Convert back into an owned `Player` only when needed.
    let owned = rkyv::deserialize::<Player, Error>(archived)?;
    assert_eq!(owned, player);
    Ok(())
}

Read an archive from a file

rkyv-badge cat-encoding-badge

Writes a Vec<Reading> to readings.rkyv in the current working directory, then reads it back and iterates over the archived readings in place.

rkyv::access requires the buffer to be suitably aligned for the archived types. fs::read returns a Vec<u8> with no alignment guarantee, so the bytes are copied into an AlignedVec before access. The archived type of Vec<Reading> is named with rkyv::Archived.

use rkyv::util::AlignedVec;
use rkyv::{Archive, Deserialize, Serialize};
use std::error::Error;
use std::fs;

#[derive(Archive, Serialize, Deserialize, Debug)]
struct Reading {
    sensor: String,
    celsius: f32,
}

fn main() -> Result<(), Box<dyn Error>> {
    let readings = vec![
        Reading {
            sensor: "kitchen".to_string(),
            celsius: 21.5,
        },
        Reading {
            sensor: "garage".to_string(),
            celsius: 8.25,
        },
    ];

    let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&readings)?;
    fs::write("readings.rkyv", &bytes)?;

    // `fs::read` gives no alignment guarantee, so copy into an `AlignedVec`.
    let mut buffer = AlignedVec::<16>::new();
    buffer.extend_from_slice(&fs::read("readings.rkyv")?);

    let archived = rkyv::access::<rkyv::Archived<Vec<Reading>>, rkyv::rancor::Error>(&buffer)?;
    for reading in archived.iter() {
        println!("{}: {:.2} °C", reading.sensor, reading.celsius.to_native());
    }
    Ok(())
}