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
- Fix the producer: ensure runtime `Value::Future` / spawn state never enters the serialized constant heap (check the pack-building path in `baml_exec::PackEnvelope`).
- Regenerate the pack/bytecode from a trusted compiler build; the loaded artifact is malformed if it contains a future object.
- 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
- Never feed live VM heap values into pack serialization; serialize only the compiled program's constant heap.
- Treat any artifact containing a `Future` payload as corrupted — regenerate from source.
- Keep spawn-state values registered with the engine registry, referenced by id rather than embedded in serializable structures.
- Add a round-trip test asserting the const heap never contains runtime-only variants.
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
- UnscheduledFuture cannot be serialized
- invalid package interface: {message}
- variant `{}` requires an explicit discriminant to stabilize
- UnscheduledFuture cannot be deserialized
- RustData cannot be serialized
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/f61eedb9bc46403c.
Report an issue: GitHub.