Structs, mutable state and constructors

Define composite types, choose between immutable and mutable, add validating constructors, and use type parameters for fast, specialised code.

Defining structs

struct Point
    x::Float64
    y::Float64
end

# immutable: no method error, construction is one error
p = Point(1.0, 2.0)
# p.x = 3.0            # ERROR: setfield!: immutable struct of type Point cannot be changed

mutable struct Counter
    value::Int
end

c = Counter(0)
c.value += 1             # fine, the field is mutable

# parametric struct: the element type is part of the type
struct Stack{T}
    items::Vector{T}
end

Stack{Int}([])           # concrete, fast
Stack([1, 2, 3])         # the type parameter is inferred
ChoiceConsequence
structImmutable; safe to share, often stack allocated
mutable structFields assignable; heap allocated, values can change under you
const fieldIn a mutable struct, fixes the field type for specialisation
struct{Foo}Parametric; one concrete type per parameter value
Base.@kwdefGenerates a keyword constructor with defaults

Constructors and validation

struct Interval
    lo::Float64
    hi::Float64

    function Interval(lo::Float64, hi::Float64)
        lo <= hi || throw(ArgumentError("lower bound must not exceed upper bound"))
        new(lo, hi)
    end
end

# an outer constructor with defaults, delegating to the inner one
Interval(hi::Float64) = Interval(0.0, hi)

Base.@kwdef struct Config
    host::String = "127.0.0.1"
    port::Int = 8000
    retries::Int = 3
end

Config(port = 9000)      # everything else takes its default

An inner constructor runs on every construction path, so it is the right place for invariants. An outer constructor cannot bypass the inner one, and that is the point: no code can create an object that violates your rules.

Field types and allocation

# BAD: an abstract field type means the compiler cannot know what is inside
struct BadBox
    value::Any
end

# GOOD: parametric, so the concrete type travels with the struct
struct Box{T}
    value::T
end

struct Pair2
    a::Float64
    b::Float64
end

isbitstype(Pair2)          # true: no pointers, can live on the stack
isbitstype(Box{Int})       # true
isbitstype(Box{String})    # false: a String holds a pointer

@allocated Box(1.0)
@allocated BadBox(1.0)
💡
A field declared as an abstract type such as Any, Number or AbstractString makes every access on it a dynamic dispatch. Use a type parameter instead: struct Box{T} keeps the concrete type in the type of the struct, and the compiler specialises on it.

FAQ

Why can I not change a field of my struct?
You declared it with struct, which is immutable. Either declare it mutable struct, or return a new instance with Base.setproperty style reconstruction, which is usually the cleaner design.
What does @kwdef actually generate?
A keyword-argument constructor plus a default for each field that has one. It is a macro convenience: the struct itself is unchanged, and the generated constructor still runs alongside the positional one.

Syntax and the type system Arrays and broadcasting in depth

Last refreshed 2026-09-18.