BoundaryML/baml · error
interface index {} is not an Object::Interface
Error message
interface index {} is not an Object::Interface What it means
This is a Rust `unreachable!()` panic inside `apply_interface_default_backfill` in baml_compiler2_emit. The compiler recorded an `InterfaceDefaultBackfill` entry pointing at an object-pool slot that, when written back, no longer holds an `Object::Interface`. It means the object pool was mutated or the recorded `ObjectIndex` was stale/reused between the point the backfill entry was created (in `build_packages`) and the point it was applied. The library panics because this is an internal invariant that should be impossible; it is never a user-input error.
Source
Thrown at baml_language/crates/baml_compiler2_emit/src/lib.rs:1228
pkg.canonicalize_impl_rules();
}
interface_default_backfill
}
/// One `InterfaceMethodDef::default` slot `build_packages` could not fill when
/// the interface was pooled (functions are pooled later), keyed by the pooled
/// interface and the method's name.
struct InterfaceDefaultBackfill {
interface: ObjectIndex,
method: Name,
default: ObjectIndex,
}
/// Write each back-filled default into its pooled `Object::Interface`.
fn apply_interface_default_backfill(program: &mut Program, backfill: &[InterfaceDefaultBackfill]) {
for entry in backfill {
let Object::Interface(iface) = &mut program.objects[entry.interface] else {
unreachable!(
"interface index {} is not an Object::Interface",
entry.interface.raw()
)
};
let method = iface
.methods
.iter_mut()
.find(|method| method.name == entry.method)
.unwrap_or_else(|| {
unreachable!(
"interface `{}` declares no method `{}` for its default",
iface.name, entry.method
)
});
method.default = Some(entry.default);
}
}
View on GitHub (pinned to bd85ce9dee)
Solutions
- Audit the code path that pushes to `interface_default_backfill` and confirm the recorded ObjectIndex is created after final pool assignment and never invalidated.
- Check whether any pass between backfill entry creation and `apply_interface_default_backfill` mutates `program.objects` (push, remove, or overwrite slots).
- Ensure `apply_interface_default_backfill` runs in the same compilation run/ordering as `generate_impl` and pool construction; no pool rebuild may occur in between.
- If triggered by an incremental/recompilation path, verify pooled object indices are rebuilt consistently and backfill entries are cleared when the pool is rebuilt.
Example fix
// before: backfill entries recorded before final pooling, indices go stale let backfill = collect_backfill(&half_built_pool); rebuild_pool(program); apply_interface_default_backfill(program, &backfill); // after: record backfill entries only after the pool is final let backfill = collect_backfill(program); // pool final; indices stable apply_interface_default_backfill(program, &backfill);
Defensive patterns
Strategy: validation
Validate before calling
// User code cannot pre-validate a compiler-internal invariant. If embedding the compiler,
// verify the pool slot before applying backfill:
fn backfill_target_is_interface(program: &Program, idx: ObjectIndex) -> bool {
matches!(program.objects.get(idx), Some(Object::Interface(_)))
} Type guard
fn is_interface(obj: &Object) -> bool {
matches!(obj, Object::Interface(_))
} Try / catch
// Rust panics are not catchable in user BAML code; when embedding the compiler, isolate
// the compile call and recover:
let result = std::panic::catch_unwind(|| compile(&source));
match result {
Ok(artifacts) => artifacts,
Err(_) => report_internal_compiler_error(),
} Prevention
- Record backfill entries only after the object pool reaches its final ordering.
- Never rebuild or reorder `program.objects` between backfill collection and application.
- Clear deferred backfill state whenever the pool is rebuilt or invalidated.
- Add debug assertions validating backfill targets immediately at record time.
When it happens
Trigger: Only hit during compilation when the deferred interface-method-default backfill pass runs with an `InterfaceDefaultBackfill` whose `interface: ObjectIndex` resolves to an object that is not `Object::Interface` — e.g. the pool slot was overwritten by another pooled object (class/enum/type-alias) or the index was built against a different pool ordering.
Common situations: Compiler development, not end-user BAML code: refactoring object pooling in baml_compiler2_emit, changing pooled-object ordering between pass phases, or reusing/shuffling ObjectIndex slots without invalidating backfill entries.
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
- sys_op callee must resolve to a statically-known global func
- exhaustive realized-leaf template classification
- interface `{}` declares no method `{}` for its default
- type-alias offset fits u32
- expected jump instruction at index {instruction_idx}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/054ab10b334ad12b.
Report an issue: GitHub.