Generic Types

Generic types let you write reusable type definitions that work with multiple concrete types. Compact supports generics for structs and circuits.

Source: User-defined types

Generic Structs

A generic struct has one or more type parameters in angle brackets:

struct Pair<A, B> {
    first: A,
    second: B
}

struct Container<T> {
    value: T,
    isSet: Boolean
}

Using Generic Structs

When using a generic struct, provide concrete type arguments:

struct Pair<A, B> {
    first: A,
    second: B
}

export pure circuit example(): Pair<Uint<64>, Boolean> {
    const p: Pair<Uint<64>, Boolean> = Pair<Uint<64>, Boolean> {
        first: 42,
        second: true
    };
    return p;
}

Multiple Type Parameters

Structs can have multiple type parameters:

struct Triple<A, B, C> {
    first: A,
    second: B,
    third: C
}

struct KeyValue<K, V> {
    key: K,
    value: V
}

Source: User-defined types

Size Parameters

In addition to type parameters, generics can have size parameters for specifying numeric values like array lengths. Size parameters are prefixed with #:

// T is a type parameter, N is a size parameter
struct FixedArray<#N, T> {
    data: Vector<N, T>,
    length: Uint<32>
}

struct Matrix<#Rows, #Cols, T> {
    data: Vector<Rows, Vector<Cols, T>>
}

Source: Primitive types

Using Size Parameters

struct Buffer<#N> {
    data: Bytes<N>,
    used: Uint<32>
}

export pure circuit createBuffer(): Buffer<64> {
    return Buffer<64> {
        data: default<Bytes<64>>,
        used: 0
    };
}

Source: Structure creation

Standard Library Generics

The standard library uses generics extensively:

// From CompactStandardLibrary:
// struct Maybe<T> { is_some: Boolean; value: T; }
// struct Either<A, B> { is_left: Boolean; left: A; right: B; }

import CompactStandardLibrary;

export pure circuit wrapValue(x: Uint<64>): Maybe<Uint<64>> {
    return some<Uint<64>>(x);
}

export pure circuit makeEither(flag: Boolean, val: Field): Either<Field, Uint<64>> {
    if (flag) {
        return left<Field, Uint<64>>(val);
    }
    return right<Field, Uint<64>>(100);
}

Source: Standard library structs, Standard library circuits

Generic Circuits

Circuits can also be generic. Note that generic circuits cannot be exported from the top level — the compiler rejects export on a type-parameterized circuit. Keep them un-exported and call them from exported, concrete circuits:

// Generic pure circuits (not exported — the compiler forbids
// exporting type-parameterized circuits from the top level)
pure circuit first<A, B>(pair: [A, B]): A {
    return pair[0];
}

pure circuit second<A, B>(pair: [A, B]): B {
    return pair[1];
}

// Using generic circuits from an exported, concrete circuit
export pure circuit example(): Field {
    const p: [Field, Boolean] = [42, true];
    return first<Field, Boolean>(p);
}

Source: Generic parameter references, Circuit and witness calls

Constraints and Limitations

No Exported Generic Circuits

A type-parameterized circuit cannot be exported from the top level (cannot export type-parameterized function from the top level, verified with toolchain 0.31.1). Export concrete wrapper circuits instead.

Type Parameter Bounds

Compact doesn’t support trait bounds or type constraints like some languages. Any type can be used as a type argument.

Generic Structs Must Be Fully Specialized

When using a generic struct as a type or in structure creation, all type and size parameters must be provided:

struct Box<T> {
    value: T
}

export pure circuit boxed(): Box<Field> {
    // All type arguments must be specified
    const b: Box<Field> = Box<Field> { value: 42 };
    return b;
}

Source: User-defined types, Structure creation

Nested Generics

Generics can be nested:

struct Node<T> {
    value: T,
    children: Vector<2, Maybe<T>>
}

struct MapEntry<K, V> {
    key: K,
    value: Maybe<V>
}

Generic Types in Ledger

Generic types work in ledger declarations:

struct Entry<V> {
    value: V,
    timestamp: Uint<64>
}

export ledger entries: Map<Bytes<32>, Entry<Uint<64>>>;

Source: Declaring public state

TypeScript Representation

Generic structs map to TypeScript generics:

// Compact
struct Pair<A, B> {
    first: A,
    second: B
}

// TypeScript (generated)
interface Pair<A, B> {
    first: A;
    second: B;
}

Source: Representations in TypeScript

Complete Example

pragma language_version >= 0.16;

import CompactStandardLibrary;

// Generic result type
struct Result<T, E> {
    success: Boolean,
    value: T,
    error: E
}

// Helper to create success result
pure circuit ok<T, E>(value: T): Result<T, E> {
    return Result<T, E> {
        success: true,
        value: value,
        error: default<E>
    };
}

// Helper to create error result
pure circuit err<T, E>(error: E): Result<T, E> {
    return Result<T, E> {
        success: false,
        value: default<T>,
        error: error
    };
}

// Generic pair operations
struct Pair<T> {
    first: T,
    second: T
}

pure circuit makePair<T>(a: T, b: T): Pair<T> {
    return Pair<T> { first: a, second: b };
}

pure circuit swap<T>(p: Pair<T>): Pair<T> {
    return Pair<T> { first: p.second, second: p.first };
}

// Size-parameterized buffer
struct FixedBuffer<#N> {
    data: Bytes<N>,
    length: Uint<32>
}

// Use the generic types
export pure circuit example(): Result<Uint<64>, Uint<8>> {
    const pair = makePair<Field>(10, 20);
    const swapped = swap<Field>(pair);

    if (swapped.first == 20) {
        return ok<Uint<64>, Uint<8>>(100);
    }
    return err<Uint<64>, Uint<8>>(1);
}

Exercises

Complete exercises/05_user_types/03_generics/.