BoundaryML/baml · critical
HeapPtr is a runtime-only pointer and must not be serialized
Error message
HeapPtr is a runtime-only pointer and must not be serialized
What it means
HeapPtr's BorshSerialize impl is intentionally a hard failure: a HeapPtr is a runtime-only heap pointer and must never appear in serialized data (a compiled Program). The VM deliberately rejects serialization of any structure containing one so a genuine bug is surfaced immediately rather than silently round-tripping a meaningless pointer.
Solutions
- Stop serializing live VM/runtime objects; only serialize the compiler-produced Program types (Object::{Function, Class, Enum, String}) which are HeapPtr-free by design.
- Audit the type being serialized for accidental inclusion of runtime pointers (heap values, futures).
- If you hit this from normal program compilation, report it — it indicates an internal invariant violation where a runtime pointer leaked into the compiled artifact.
- Strip/detach runtime-only fields before serializing your own structures.
Example fix
// before: serializing a runtime struct that embeds a HeapPtr
#[derive(BorshSerialize)]
struct Snapshot { obj: HeapPtr }
// after: serialize only HeapPtr-free Program types
#[derive(BorshSerialize)]
struct Snapshot { name: Object_String } Defensive patterns
Strategy: type-guard
Validate before calling
// Reject runtime graphs containing HeapPtr before attempting serialization
fn serializable_check(v: &VmValue) -> bool {
!matches!(v, VmValue::Heap(_))
} Prevention
- Serialize only compiler-produced Program types (Object::{Function, Class, Enum, String}).
- Never attempt to persist live VM heap state or runtime object graphs.
- Audit custom serialization paths for embedded HeapPtr fields.
- Treat occurrences as bugs and report them rather than working around.
When it happens
Trigger: Calling borsh serialization on a runtime object graph that still contains HeapPtr-backed values (e.g. attempting to persist/serialize live VM state or an object not meant for compiled programs).
Common situations: Trying to cache or ship serialized VM heap state; a compiler/VM bug placing a runtime-only object into the serialized Program; custom tooling serializing Value graphs that were never supposed to leave the runtime.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Future cannot be serialized
- HeapPtr cannot be deserialized: runtime pointer leaked into…
- failed to decode
- failed to encode
- Future cannot be deserialized
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/54e64271047ad9d4.
Report an issue: GitHub.
Appendix: source
Thrown at baml_language/crates/bex_vm_types/src/heap_ptr.rs:163
#[cfg(feature = "heap_debug")]
#[inline]
pub fn epoch(self) -> u32 {
self.epoch
}
}
// `HeapPtr` is a runtime-only address into the live heap. It must never
// reach a serialized payload (e.g. a borsh-encoded `Program` in a pack
// envelope) — the addresses are meaningless outside the originating
// process, and a deserialized non-null `HeapPtr` would be undefined
// behavior on first deref. The borsh impls below therefore *fail* at the
// boundary instead of round-tripping to `null()`: every type the compiler
// actually puts into a serialized `Program` (`Object::{Function, Class,
// Enum, String}`) is HeapPtr-free, so any path that does encounter one
// reflects a real bug we want to surface immediately rather than mask.
impl BorshSerialize for HeapPtr {
fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"HeapPtr is a runtime-only pointer and must not be serialized",
))
}
}
impl BorshDeserialize for HeapPtr {
fn deserialize_reader<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"HeapPtr cannot be deserialized: runtime pointer leaked into serialized data",
))
}
}
impl PartialEq for HeapPtr {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptrView on GitHub (pinned to bd85ce9dee)