Skip to content

Serialization

Any Value can be turned into a byte buffer and back. This is useful for persistence, caching, and moving data between processes — and it is the same wire format the IPC layer uses on the network.

Assume a live runtime

use rayforce::{Runtime, Value};
// every snippet below runs inside:
Runtime::scope(|rt| { /* … */ })?;

Serialize and deserialize

  • value.serialize() -> Result<Vec<u8>> encodes a Value into bytes.
  • Value::deserialize(&[u8]) -> Result<Value> decodes bytes back into a Value.
use rayforce::{Runtime, Value};
Runtime::scope(|_rt| {
    let v = Value::i64(123456789);
    let bytes = v.serialize()?;
    let restored = Value::deserialize(&bytes)?;

    assert_eq!(restored.as_i64()?, 123456789);
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

A round-trip preserves the value exactly — including its formatting:

use rayforce::{Runtime, Value};
Runtime::scope(|_rt| {
    let v = Value::vec(&[7i64, 8, 9]);
    let back = Value::deserialize(&v.serialize()?)?;
    assert_eq!(v.format(), back.format());
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

Atoms

Every atom type round-trips:

use rayforce::{Runtime, Value};
Runtime::scope(|_rt| {
    fn roundtrip(v: &Value) -> rayforce::Result<Value> {
        Value::deserialize(&v.serialize()?)
    }

    assert_eq!(roundtrip(&Value::f64(123.456))?.as_f64()?, 123.456);
    assert_eq!(roundtrip(&Value::sym("hello"))?.as_sym()?, "hello");
    assert_eq!(roundtrip(&Value::string("a string"))?.as_string()?, "a string");
    assert!(roundtrip(&Value::bool(true))?.as_bool()?);
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

Vectors

Vectors round-trip element-for-element; numeric vectors read back zero-copy:

use rayforce::{Runtime, Value};
Runtime::scope(|_rt| {
    let v = Value::vec(&[1i64, 2, 3, 4, 5]);
    let back = Value::deserialize(&v.serialize()?)?;
    assert_eq!(back.as_slice::<i64>()?, &[1, 2, 3, 4, 5]);

    let syms = Value::sym_vec(&["a", "bb", "ccc"]);
    let back = Value::deserialize(&syms.serialize()?)?;
    assert_eq!(back.len(), 3);
    assert_eq!(back.get(1)?.as_sym()?, "bb");
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

Tables

Tables (and lists and dicts) round-trip just the same. Serialize through the table's underlying value and rebuild it on the other side:

use rayforce::{Runtime, Table, Value};
Runtime::scope(|_rt| {
    let t = Table::new(
        &["sym", "px"],
        &[
            Value::sym_vec(&["AAPL", "MSFT"]),
            Value::vec(&[100.0f64, 200.0]),
        ],
    )?;

    let bytes = t.as_value().serialize()?;
    let restored = Value::deserialize(&bytes)?.as_table()?;

    assert_eq!(restored.shape(), (2, 2));
    assert_eq!(restored.column("px")?.as_slice::<f64>()?, &[100.0, 200.0]);
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

Invalid input is an error, not a panic

Value::deserialize returns Result. Feeding it bytes that are not a valid payload yields an Err rather than undefined behavior:

use rayforce::{Runtime, Value};
Runtime::scope(|_rt| {
    assert!(Value::deserialize(&[0u8, 1, 2, 3, 4, 5, 6, 7]).is_err());
    Ok(())
})?;
# Ok::<(), rayforce::RayError>(())

See also

  • IPC — uses this exact format over the wire.