Skip to content
AgileLogics
5 min read

Constructors

Prerequisites: Structs

The automatic constructor takes one argument per field. You can add your own constructors — outer constructors are just extra methods:

struct Color
    r::UInt8
    g::UInt8
    b::UInt8
end

Color(gray::Integer) = Color(gray, gray, gray)

function Color(hex::AbstractString)
    h = lstrip(hex, '#')
    return Color(parse(UInt8, h[1:2], base = 16),
                 parse(UInt8, h[3:4], base = 16),
                 parse(UInt8, h[5:6], base = 16))
end

println(Color(10, 20, 30))
println(Color(128))
println(Color("#ff8800"))
Color(0x0a, 0x14, 0x1e)
Color(0x80, 0x80, 0x80)
Color(0xff, 0x88, 0x00)

Try the hex to RGB converter to check colors like these.

Inner constructors enforce rules

An inner constructor is defined inside the struct and replaces the automatic one. Use it to validate data, so an invalid object can never exist:

struct Percentage
    value::Float64
    function Percentage(v)
        0 <= v <= 100 || throw(DomainError(v, "must be between 0 and 100"))
        return new(v)
    end
end

println(Percentage(42).value)
try
    Percentage(120)
catch err
    println(sprint(showerror, err))
end
42.0
DomainError with 120:
must be between 0 and 100

Inside an inner constructor, new(...) creates the object.

Keyword constructors with @kwdef

For structs with many fields, Base.@kwdef generates a keyword-argument constructor with defaults:

Base.@kwdef struct Config
    host::String = "localhost"
    port::Int = 8080
    debug::Bool = false
end

println(Config())
println(Config(port = 3000, debug = true))
Config("localhost", 8080, false)
Config("localhost", 3000, true)

Practice

Write a Fraction struct with integer num and den whose inner constructor rejects a zero denominator and reduces the fraction with gcd, so Fraction(6, 8) stores 3/4.

Show solution
struct Fraction
    num::Int
    den::Int
    function Fraction(n, d)
        d == 0 && throw(ArgumentError("denominator is zero"))
        g = gcd(n, d) * sign(d)
        return new(n ÷ g, d ÷ g)
    end
end

Base.show(io::IO, f::Fraction) = print(io, f.num, "/", f.den)
println(Fraction(6, 8), " ", Fraction(3, -9))
3/4 -1/3

(Julia already has exact fractions built in — 6 // 8 — but writing your own is good practice.)