denoland/deno · error · TypeError
Cannot get pointer size: found recursive struct
Error message
Cannot get pointer size: found recursive struct
What it means
Struct types used in FFI (e.g. as nonblocking call results, where UnsafeFnPointer computes sizes) are laid out by recursively summing field sizes/alignments with a per-computation cache. A struct that contains itself directly or through another struct has no finite size; the recursion is detected via a cache sentinel and reported as a TypeError. This mirrors C compilers rejecting infinitely-sized struct definitions.
Source
Thrown at ext/ffi/00_ffi.js:344
buffer,
);
return buffer;
}
}
}
}
function isStruct(type) {
return typeof type === "object" && type !== null &&
typeof type.struct === "object";
}
function getTypeSizeAndAlignment(type, cache = new SafeMap()) {
if (isStruct(type)) {
const cached = cache.get(type);
if (cached !== undefined) {
if (cached === null) {
throw new TypeError(
"Cannot get pointer size: found recursive struct",
);
}
return cached;
}
cache.set(type, null);
let size = 0;
let alignment = 1;
for (const field of new SafeArrayIterator(type.struct)) {
const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment(
field,
cache,
);
alignment = MathMax(alignment, fieldAlign);
size = MathCeil(size / fieldAlign) * fieldAlign;
size += fieldSize;
}
size = MathCeil(size / alignment) * alignment;View on GitHub (pinned to 89f33cbef2)
Solutions
- Replace the self-reference with pointer indirection: type the field as "pointer" (struct node *next in C), not the struct itself
- Break mutual cycles the same way: at least one edge in the cycle must be a pointer
- Re-check that nested struct fields are { struct: [...] } objects rather than accidental back-references
Example fix
// before
const node = { struct: ["u32"] };
node.struct.push(node); // recursive struct
// after
const node = {
struct: ["u32", { struct: ["u32"] } /* inline next->val */],
};
// C: struct node { uint32_t val; struct node *next; } -> model next as "pointer":
const nodeFixed = { struct: ["u32", "pointer"] }; Defensive patterns
Strategy: validation
Validate before calling
function assertAcyclicStruct(type, seen = new Set()) {
if (typeof type === "object" && type !== null && type.struct) {
if (seen.has(type)) throw new Error("recursive FFI struct type");
seen.add(type);
for (const field of type.struct) assertAcyclicStruct(field, seen);
}
}
assertAcyclicStruct(myStructType); // run before handing it to FFI Try / catch
try {
const fn = new Deno.UnsafeFnPointer(ptr, { parameters: [], result: myStructType });
} catch (err) {
if (err instanceof TypeError && err.message === "Cannot get pointer size: found recursive struct") {
// replace the self-reference with "pointer" and rebuild the type object
} else throw err;
} Prevention
- Model C self-references as "pointer" fields, never as the struct itself
- Build struct types as fresh literals (no mutation of a const you later nest)
- Run a cheap cycle check over type objects when types come from data or generators
- Remember mutually recursive structs are equally invalid
When it happens
Trigger: Defining a type object whose struct array references the same object (const node = { struct: ["pointer", node] }) or a cycle through multiple structs (A contains B, B contains A), then using it where layout is needed.
Common situations: Modeling linked-list or tree nodes from C headers; building type objects mutably and accidentally closing a cycle; splitting one logical struct into mutually referencing parts.
Related errors
- Cannot get pointer size, unsupported type: ${type}
- Object is not a valid image or a path to an image. `Deno.jup
- Cannot access pointer: expected 'ArrayBuffer', 'SharedArrayB
- Cannot construct UnsafeCallback: cannot be nonblocking
- Foreign symbol of type 'void' is not supported
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/8cc616f8fa89e0dc.
Report an issue: GitHub.