clockworklabs/SpacetimeDB · error

recursive types not supported

Error message

recursive types not supported

What it means

`resolved_type_via_v9<T>()` builds a raw module-def for T and resolves all type references; `resolve_refs` fails (triggering this expect) when the typespace still contains unresolvable refs, which is exactly what recursive types produce — a ref cycle can never be inlined into a single AlgebraicType. SpacetimeDB's type system does not support recursive schema types, so this is a hard error by design.

Source

Thrown at crates/lib/src/lib.rs:341

    } else {
        hex
    };
    hex::FromHex::from_hex(hex)
}

/// Returns a resolved `AlgebraicType` (containing no `AlgebraicTypeRefs`) for a given `SpacetimeType`,
/// using the v9 moduledef infrastructure.
/// Panics if the type is recursive.
///
/// TODO: we could implement something like this in `sats` itself, but would need a lightweight `TypespaceBuilder` implementation there.
pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
    let mut builder = RawModuleDefV9Builder::new();
    let ty = T::make_type(&mut builder);
    let module = builder.finish();

    WithTypespace::new(&module.typespace, &ty)
        .resolve_refs()
        .expect("recursive types not supported")
}

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Break the cycle: model self-reference through table rows and row ids instead of nested types.
  2. Flatten the structure: store nodes in a table (id, value, parent_id) or use an explicit arena (Vec<Node> + usize links) for internal logic.
  3. Keep recursive types out of SpacetimeType definitions entirely; use them only in module-internal, non-schema code.

Example fix

// before: recursive type — resolve_refs can never finish
#[derive(spacetimedb::SpacetimeType)]
struct Node { value: u64, next: Option<Box<Node>> }

// after: normalize via a table keyed by id
#[derive(spacetimedb::SpacetimeType)]
struct Node { id: u64, value: u64, parent: Option<u64> } // rows in table `node`
Defensive patterns

Strategy: validation

Validate before calling

// CI test: any type passed to resolved_type_via_v9 must resolve
#[test]
fn schema_resolves() { let _ = resolved_type_via_v9::<MyType>(); }

Type guard

trait NonRecursiveType: spacetimedb::SpacetimeType {} // marker implemented only for types covered by the resolving test

Prevention

When it happens

Trigger: Calling `resolved_type_via_v9` (or APIs that derive schema from a type) with T that transitively contains itself: `struct Node { next: Option<Box<Node>> }`, mutually recursive structs/enums, or any type whose expansion does not terminate.

Common situations: Porting linked-list/tree types from general Rust into SpacetimeDB modules; reusing recursive serde types as table rows, reducer args, or return values; tooling that inspects arbitrary user-supplied types.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/b4b5dae408ebd752. Report an issue: GitHub.