BoundaryML/baml · error

Future cannot be deserialized

Error message

Future cannot be deserialized

What it means

`bex_vm_types::types::future::Future` is a runtime-only spawn-state object (atomic state machine, cancellation token, uninitialized MaybeUninit value slot) that never appears in a compiled `Program`. Its `BorshDeserialize` impl is deliberately a stub that always returns `Err(InvalidData)`: the crate wants a malformed program (one whose constant heap or pack payload claims to contain a `Future`) to fail fast rather than produce a bogus runtime object.

Source

Thrown at baml_language/crates/bex_vm_types/src/types/future.rs:130

// on `state` and the single-writer invariant enforced by the
// `FutureManager`'s state mutex.
unsafe impl Send for Future {}
unsafe impl Sync for Future {}

// Futures are runtime-only; they never appear in a compiled Program. Reject
// serialization explicitly so a malformed program fails fast.
impl BorshSerialize for Future {
    fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "Future cannot be serialized",
        ))
    }
}

impl BorshDeserialize for Future {
    fn deserialize_reader<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "Future cannot be deserialized",
        ))
    }
}

// `UnscheduledFuture` is a runtime spawn-request slot — same lifecycle
// shape as `Future`, never appears in a compiled `Program`. The pack
// envelope (`baml_exec::PackEnvelope`) serializes the bytecode + the
// constant heap; if an `UnscheduledFuture` ever reaches the serializer
// that's a malformed program and we want to fail fast.
impl BorshSerialize for UnscheduledFuture {
    fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "UnscheduledFuture cannot be serialized",
        ))
    }

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Fix the producer: ensure runtime `Value::Future` / spawn state never enters the serialized constant heap (check the pack-building path in `baml_exec::PackEnvelope`).
  2. Regenerate the pack/bytecode from a trusted compiler build; the loaded artifact is malformed if it contains a future object.
  3. If you need cross-process future state, redesign to persist an ID/handle and re-register it with the engine registry on load, not the `Future` struct itself.

Example fix

// before: deserializing a heap snapshot that contains a Future
let heap: ConstantHeap = borsh::from_reader(&mut reader)?; // io::Error: Future cannot be deserialized
// after: keep futures runtime-only; serialize only const-heap values
assert!(!value.is_future(), "futures must not enter the constant heap");
let heap: ConstantHeap = borsh::from_reader(&mut reader)?;
Defensive patterns

Strategy: validation

Validate before calling

// before deserializing any heap/pool payload
fn heap_payload_contains_future(wire: &[ObjectWire]) -> bool {
    wire.iter().any(|o| matches!(o, ObjectWire::Future(_) | ObjectWire::UnscheduledFuture(_)))
}
// if heap_payload_contains_future(&pool) { return Err("malformed pack: runtime-only value"); }

Type guard

fn is_runtime_only(o: &ObjectWire) -> bool {
    matches!(o, ObjectWire::Future(_) | ObjectWire::UnscheduledFuture(_))
}

Prevention

When it happens

Trigger: Calling `Future::deserialize_reader` / `borsh::from_reader::<Future>` or deserializing any wire structure (e.g. `ObjectWire::Future`, `Object`) whose payload routes into `Future`'s Borsh impl. There is no code path that ever returns `Ok`.

Common situations: Loading a corrupted or hand-crafted BAML pack (`baml_exec::PackEnvelope`) whose object pool encodes a future object; a VM/tooling bug that lets a runtime `Value::Future` leak into the constant heap that gets serialized at pack time and then deserialized on load; fuzzing or round-trip tests of the wire format.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/f61eedb9bc46403c. Report an issue: GitHub.