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

  1. Replace the self-reference with pointer indirection: type the field as "pointer" (struct node *next in C), not the struct itself
  2. Break mutual cycles the same way: at least one edge in the cycle must be a pointer
  3. 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

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


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/8cc616f8fa89e0dc. Report an issue: GitHub.