Skip to content
AgileLogics
6 min read

Structs

Prerequisites: Multiple dispatch

A struct defines a new composite type — a named group of fields:

struct Point
    x::Float64
    y::Float64
end

p = Point(3, 4)
println(p)
println(p.x, " ", p.y)
println(fieldnames(Point))
println(typeof(p), " ", p isa Point)

distance(a::Point, b::Point) = sqrt((a.x - b.x)^2 + (a.y - b.y)^2)
println(distance(Point(0, 0), p))
Point(3.0, 4.0)
3.0 4.0
(:x, :y)
Point true
5.0
  • Julia creates a constructor Point(x, y) automatically and converts the arguments to the field types (3 became 3.0).
  • Methods live outside the struct. There are no classes: data is in structs, behavior is in functions that dispatch on them.

Immutable by default

struct Point
    x::Float64
    y::Float64
end

p = Point(1, 2)
try
    p.x = 10
catch err
    println(sprint(showerror, err))
end
moved = Point(p.x + 1, p.y)
println(moved)
println(Point(1, 2) == Point(1, 2))
setfield!: immutable struct of type Point cannot be changed
Point(2.0, 2.0)
true

Immutable structs are safe to share and very fast — small ones are stored inline in arrays, just like numbers. Two immutable values with equal fields are equal.

mutable struct

mutable struct Account
    owner::String
    balance::Float64
end

function deposit!(acc::Account, amount)
    amount > 0 || throw(ArgumentError("amount must be positive"))
    acc.balance += amount
    return acc
end

acc = Account("Ada", 100)
deposit!(acc, 50)
println(acc.balance)
println(Account("Ada", 1) == Account("Ada", 1))
150.0
false

Use mutable struct when an object's state genuinely changes over time. Mutable objects have identity: two separate accounts with the same fields aren't == unless you define == for them.

Custom printing

struct Temperature
    celsius::Float64
end

Base.show(io::IO, t::Temperature) = print(io, round(t.celsius, digits = 1), " °C")

t = Temperature(21.456)
println(t)
println([Temperature(0), Temperature(100)])
println("It's $t outside")
21.5 °C
Temperature[0.0 °C, 100.0 °C]
It's 21.5 °C outside

Defining Base.show(io::IO, x::YourType) controls how your type prints everywhere — in println, in arrays, in interpolation, and in the REPL.

Redefining structs

Before Julia 1.12, redefining a struct with different fields in a running session was an error and you had to restart Julia. Since 1.12 it's allowed: new code uses the new definition, while objects created earlier keep the old type. Revise.jl tracks such changes in your files automatically.

Practice

Define a Rectangle struct with width and height, functions area and perimeter, and a show method that prints Rectangle 3×4.

Show solution
struct Rectangle
    width::Float64
    height::Float64
end

area(r::Rectangle) = r.width * r.height
perimeter(r::Rectangle) = 2 * (r.width + r.height)
Base.show(io::IO, r::Rectangle) = print(io, "Rectangle ", r.width, "×", r.height)

r = Rectangle(3, 4)
println(r, ": area ", area(r), ", perimeter ", perimeter(r))
Rectangle 3.0×4.0: area 12.0, perimeter 14.0