denoland/deno · error
Unable to deserialize result parameter.
Error message
Unable to deserialize result parameter.
What it means
For Deno FFI callbacks (Deno.UnsafeCallback) whose native result type is a pointer or buffer, the value returned by the JS callback must be a typed array or ArrayBuffer so its data pointer can be copied to the native caller. If the callback returns anything else for such a result type (undefined, null, plain object, string), the FFI bridge panics with this message. The panic happens across the native boundary, so a JS try/catch cannot intercept it.
Source
Thrown at ext/ffi/callback.rs:551
{
let byte_offset = value.byte_offset();
let ab = value
.buffer(scope)
.expect("Unable to deserialize result parameter.");
size = value.byte_length();
ab.data()
.expect("Unable to deserialize result parameter.")
.as_ptr()
.add(byte_offset)
} else if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(value)
{
size = value.byte_length();
value
.data()
.expect("Unable to deserialize result parameter.")
.as_ptr()
} else {
panic!("Unable to deserialize result parameter.");
};
std::ptr::copy_nonoverlapping(
pointer as *mut u8,
result as *mut u8,
std::cmp::min(size, (*cif.rtype).size),
);
}
NativeType::Void => {
// nop
}
};
}
}
#[op2]
pub fn op_ffi_unsafe_callback_ref(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Make the callback return a Uint8Array (or matching TypedArray) for buffer results on every code path, including error branches
- Use `result: "pointer"` with a BigInt address only when you truly manage that memory yourself
- Return `new Uint8Array(0)` instead of null when there is no data
- Cover every callback branch in tests; the panicking path is the one you did not test
Example fix
// before — buffer result, but the error branch returns undefined
const cb = new Deno.UnsafeCallback(
{ parameters: ["i32"], result: "buffer" },
(code: number) => code > 0 ? new Uint8Array([code]) : null,
);
// after — every path returns a TypedArray
const cb = new Deno.UnsafeCallback(
{ parameters: ["i32"], result: "buffer" },
(code: number) => new Uint8Array([code & 0xff]),
); Defensive patterns
Strategy: type-guard
Validate before calling
const cb = new Deno.UnsafeCallback(
{ parameters: [], result: "buffer" },
() => {
const v = compute();
return v instanceof Uint8Array ? v : new Uint8Array(0); // never panic the bridge
},
); Type guard
function isFFIBufferResult(v: unknown): v is Uint8Array | ArrayBuffer {
return v instanceof ArrayBuffer ||
(ArrayBuffer.isView(v) && !(v instanceof DataView));
} Prevention
- Every code path of a buffer-result callback must return a TypedArray or ArrayBuffer
- Use `new Uint8Array(0)` for empty results instead of null/undefined
- Return a BigInt only for explicitly declared pointer results
- Cover error branches in callback tests — the untested branch is where the panic lives
When it happens
Trigger: A Deno.UnsafeCallback declared with result: "buffer" (or another pointer-backed result type) whose JS function returns undefined/null/a plain value on at least one code path; returning a number is reinterpreted as a raw pointer, which is usually a second bug waiting to happen.
Common situations: Porting C callback contracts to Deno FFI; error branches in the JS callback that forget to return a buffer; refactors changing what the callback returns while the FFI signature stays the same.
Related errors
- Cannot construct UnsafeCallback: cannot be nonblocking
- Initialize libloading failed {}
- Invalid data type at index {id}, expected '{}'
- unable to convert
- Cannot access pointer: expected 'ArrayBuffer', 'SharedArrayB
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/d4c3d79af0c49d28.
Report an issue: GitHub.