Comparison

Julia has several macros for cutting boilerplate out of struct definitions. This page compares Terse.jl with Base.@kwdef, QuickTypes.jl, Parameters.jl, and ConcreteStructs.jl.

Feature Matrix

Feature @kwdef Quick­Types Param­eters Concrete­Structs Terse
Concise syntax ✓ ✓ ✓
Default values ✓ ✓ ✓ ✓
Keyword constructors ✓ ✓ ✓ ✓
Mutable structs ✓ ✓ ✓ ✓ ✓
Parametric types ✓ ✓ ✓ ✓ ᵃ ✓
Abstract supertypes ✓
Whole hierarchy at once ✓
Nested hierarchies ✓
Per-type mutability ✓
Const fields ✓
Computed constructors ✓
Hidden fields ✓
Escape hatch ✓
Source lines — ~540 ~670 ~245 ~280
Dependencies — 2 ᵇ 2 ᶜ 0 0

ᵃ type parameters inferred automatically
ᵇ ConstructionBase, MacroTools
ᶜ OrderedCollections, UnPack

Side by Side Examples

The same hierarchy written five ways: an abstract Animal with a Cat (default lives = 9) and a mutable Dog with a const name field and a keyword argument.

Terse.jl

using Terse

@types Animal > (
    Cat(lives::Int = 9),
    @mutable Dog(@const(name::String); pointy_ears::Bool = true)
)

Cat(), Dog("Rex")
(Cat(lives=9), Dog(name="Rex", pointy_ears=true))

QuickTypes.jl

One type at a time; @qmutable has no const field support, so Dog falls back to plain Julia.

abstract type Animal end

@qstruct Cat(lives::Int = 9) <: Animal

mutable struct Dog <: Animal
    const name::String
    pointy_ears::Bool
    Dog(name::String; pointy_ears::Bool = true) = new(name, pointy_ears)
end

Parameters.jl

Block syntax; no const field support in @with_kw.

abstract type Animal end

@with_kw struct Cat <: Animal
    lives::Int = 9
end

mutable struct Dog <: Animal
    const name::String
    pointy_ears::Bool
    Dog(name::String; pointy_ears::Bool = true) = new(name, pointy_ears)
end

Base.@kwdef

No extra dependencies; no const field support in @kwdef.

abstract type Animal end

Base.@kwdef struct Cat <: Animal
    lives::Int = 9
end

mutable struct Dog <: Animal
    const name::String
    pointy_ears::Bool
    Dog(name::String; pointy_ears::Bool = true) = new(name, pointy_ears)
end

ConcreteStructs.jl

@concrete infers type parameters from untyped fields rather than generating constructors, so defaults, keyword arguments, and const fields are all written by hand.

abstract type Animal end

@concrete struct Cat <: Animal
    lives
end
Cat() = Cat(9)

mutable struct Dog <: Animal
    const name::String
    pointy_ears::Bool
    Dog(name::String; pointy_ears::Bool = true) = new(name, pointy_ears)
end