API Stability Notice

Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.

Default

The Default macro generates a static defaultValue() factory method that creates instances with default values. This is analogous to Rust’s Default trait, providing a standard way to create “zero” or “empty” instances of types.

Generated Output

TypeGenerated CodeDescription
Classstatic defaultValue() + classNameDefaultValue()Static factory method + standalone function
EnumenumNameDefaultValue(): EnumNameStandalone function returning @default variant
InterfaceifaceNameDefaultValue(): InterfaceNameStandalone function returning object literal
Type AliastypeNameDefaultValue(): TypeNameStandalone function with type-appropriate default

Names use camelCase conversion (e.g., UserSettings -> userSettingsDefaultValue).

Default Values by Type

The macro uses Rust-like default semantics:

TypeDefault Value
string"" (empty string)
number0
booleanfalse
bigint0n
T[][] (empty array)
Array<T>[] (empty array)
Map<K,V>new Map()
Set<T>new Set()
Datenew Date() (current time)
T \| nullnull
Unions (A \| B)Default of the first primitive or literal member, else the first member’s default
CustomTypecustomTypeDefaultValue() (recursive standalone-function call)

Field-Level Options

The @default decorator allows specifying explicit default values:

  • @default(42) - Use 42 as the default
  • @default("hello") - Use “hello” as the default
  • @default([]) - Use empty array as the default
  • @default({ value: "test" }) - Named form for complex values

Example

Before (Your Code)
/** @derive(Default) */
class UserSettings {
    /** @default("light") */
    theme: string;

    /** @default(10) */
    pageSize: number;

    notifications: boolean;  // Uses type default: false
}
After (Generated)
class UserSettings {
    theme: string;

    pageSize: number;

    notifications: boolean; // Uses type default: false

    static defaultValue(): UserSettings {
        const instance: UserSettings = Object.create(UserSettings.prototype);
        instance.theme = 'light';
        instance.pageSize = 10;
        instance.notifications = false;
        return instance;
    }
}

export function userSettingsDefaultValue(): UserSettings {
    return UserSettings.defaultValue();
}

Enum Defaults

For enums, mark one variant with @default:

Before (Your Code)
/** @derive(Default) */
enum Status {
    /** @default */
    Pending,
    Active,
    Completed
}
After (Generated)
enum Status {
    /** @default */
    Pending,
    Active,
    Completed
}

export function statusDefaultValue(): Status {
    return Status.Pending;
}

Like Rust’s derive, the class default runs no constructor (so it works beside Decode’s, which takes arguments). Each field gets its @default(value), else its initializer as written, else its type’s default above. An optional field with neither @default nor an initializer is left out, and a static property is never a field.

/** @derive(Default) */
class Retry {
    attempts: number = 3;    // 3, its initializer
    /** @default(500) */
    delayMs: number = 100;   // 500, @default wins
    label?: string;          // left out
}

Primitive Aliases

A type alias of a primitive, plain or branded with $Newtype, defaults to the primitive’s default above, or to its @default(value), cast to the alias:

/** @derive(Default) */
type Meters = $Newtype<number>;

export function metersDefaultValue(): Meters {
    return 0 as Meters;
}

An alias with @endec validators must give @default(value): the primitive’s default may fail them, just as a Rust newtype with no valid zero derives no Default.

/** @derive(Decode, Default) */
/** @endec(positive) */
/** @default(1) */
type Meters = $Newtype<number>;

Error Handling

The macro reports an expansion error only if:

  • An enum has no variant marked with @default
  • A type alias that is not an object, union or primitive has no @default(value)
  • A primitive alias with @endec validators has no @default(value)
  • A field’s @default expression fails to parse
  • A function-typed field has neither @default(value) nor an initializer, as a Rust fn pointer has no Default
  • A field’s initializer reads this, which a default has no instance for

Missing defaults on custom-typed fields are not expansion errors: every type is assumed to implement Default (Rust-like philosophy), so the macro emits a typeNameDefaultValue() call for any custom type. If a field’s type is in the type registry but does not derive Default, a warning is printed to stderr at expansion time and the call is generated anyway. It may then fail at runtime if the function does not exist.