Structs

Structs (structures) let you create custom composite types with named fields. They’re ideal for grouping related data together.

Source: User-defined types

Declaring a Struct

Use the struct keyword followed by the type name and field definitions:

struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

Fields are separated by commas (or semicolons). A trailing separator is allowed but not required.

Source: User-defined types

Creating Struct Values

Create a struct value using the struct name followed by field values in braces:

struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

export pure circuit createPlayers(ownerKey: Bytes<32>): Player {
    // Named fields (recommended for clarity)
    const player = Player {
        address: ownerKey,
        score: 0,
        isActive: true
    };

    // Positional fields (order must match declaration)
    const player2 = Player { ownerKey, 0, true };

    // Mixed: positional first, then named
    const player3 = Player { ownerKey, 0, isActive: true };

    return player;
}

Source: Structure creation

Accessing Fields

Use dot notation to access struct fields:

struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

export pure circuit readFields(ownerKey: Bytes<32>): Boolean {
    const player = Player { address: ownerKey, score: 100, isActive: true };

    const addr = player.address;      // Bytes<32>
    const points = player.score;      // Uint<64>
    const active = player.isActive;   // Boolean

    return active;
}

Source: Element and member access expressions

Default Values

The default value of a struct has all fields set to their type’s default:

struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

export pure circuit emptyPlayer(): Player {
    const empty = default<Player>;
    // Equivalent to: Player { address: default<Bytes<32>>, score: 0, isActive: false }
    return empty;
}

Source: Default values

Nested Structs

Structs can contain other structs. Use chained dot notation to access nested fields:

struct Coordinate {
    x: Field,
    y: Field
}

struct Rectangle {
    topLeft: Coordinate,
    bottomRight: Coordinate
}

export pure circuit area(rect: Rectangle): Field {
    const width = rect.bottomRight.x - rect.topLeft.x;
    const height = rect.bottomRight.y - rect.topLeft.y;
    return width * height;
}

Source: User-defined types

Structs in Maps

Structs can be used as values in Map ledger types:

struct UserData {
    balance: Uint<64>,
    lastAction: Uint<64>,
    isVerified: Boolean
}

export ledger users: Map<Bytes<32>, UserData>;

Source: Nested state types in Map

Exporting Structs

To use a struct from TypeScript, export it:

export struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

Source: Top-level exports

TypeScript Representation

Exported structs map to TypeScript objects with the same field names:

// Compact
export struct Player {
    address: Bytes<32>,
    score: Uint<64>,
    isActive: Boolean
}

// TypeScript representation
interface Player {
    address: Uint8Array;  // Bytes<32>
    score: bigint;        // Uint<64>
    isActive: boolean;    // Boolean
}

Source: Representations in TypeScript

Recursive Structs Are Not Allowed

Structs cannot contain themselves directly or indirectly:

// ❌ ERROR: Recursive struct
struct Node {
    value: Field,
    next: Node  // Not allowed!
}

// ❌ ERROR: Mutually recursive
struct Even {
    predecessor: Odd
}

struct Odd {
    predecessor: Even  // Creates cycle - not allowed!
}

Source: User-defined types

Complete Example

pragma language_version >= 0.16;

// Define structs
struct PlayerStats {
    wins: Uint<32>,
    losses: Uint<32>,
    draws: Uint<32>
}

struct Player {
    address: Bytes<32>,
    name: Bytes<32>,
    stats: PlayerStats
}

// Pure circuit to create a new player
export pure circuit createPlayer(
    address: Bytes<32>,
    name: Bytes<32>
): Player {
    return Player {
        address: address,
        name: name,
        stats: PlayerStats { wins: 0, losses: 0, draws: 0 }
    };
}

// Pure circuit to update stats after a win
export pure circuit recordWin(player: Player): Player {
    const newStats = PlayerStats {
        wins: (player.stats.wins + 1) as Uint<32>,
        losses: player.stats.losses,
        draws: player.stats.draws
    };
    return Player {
        address: player.address,
        name: player.name,
        stats: newStats
    };
}

Exercises

Complete exercises/05_user_types/01_structs/.