Defining Structs
A struct in Julia declares a new composite type with named, typed fields — for example, struct Point x::Float64; y::Float64 end defines a Point type with two Float64 fields. By default, structs are immutable: once a Point is constructed, its x and y fields cannot be reassigned, which lets the compiler store instances more efficiently (often directly on the stack or inline inside containers) and reason more confidently about the code. Julia automatically generates a default positional constructor, so Point(1.0, 2.0) works immediately without writing any constructor code yourself.
Cricket analogy: Defining struct Point x::Float64; y::Float64 end is like fixing a player's scorecard template to always have exactly two typed fields — runs and balls faced — before a single innings is recorded, giving the scorer a guaranteed structure to fill in.
Mutable Structs and Constructors
Prefixing the declaration with mutable struct allows fields to be reassigned after construction, at the cost of the extra indirection and allocation that heap-allocated, mutable objects require. Custom inner constructors — defined inside the struct body using the special new() function — let you validate or transform arguments before constructing the instance, for example rejecting a negative radius in a Circle struct; outer constructors, defined outside the struct body as ordinary methods, add convenience forms like a default constructor that fills in a missing field, without touching the type's core definition.
Cricket analogy: A mutable struct is like a live scoreboard display that gets updated ball by ball throughout an innings, unlike a printed final scorecard; the extra electronics and refresh mechanism (heap allocation) are the cost of allowing those in-place updates.
struct Circle
radius::Float64
# inner constructor validates the argument before construction
function Circle(radius::Float64)
radius < 0 && throw(ArgumentError("radius must be non-negative"))
new(radius)
end
end
# outer constructor: convenience method accepting an Int
Circle(radius::Int) = Circle(Float64(radius))
area(c::Circle) = π * c.radius^2
c = Circle(2) # uses the outer constructor, then the validating inner one
area(c) # 12.566...Parametric Types
Structs can be parameterized over types, as in struct Point{T} x::T; y::T end, letting a single definition serve Point{Int64}, Point{Float64}, or even Point{Rational{Int}} while keeping each field concretely typed for that instance. This matters for performance: a field declared as T (a type parameter bound to a concrete type at construction) lets the compiler generate specialized, unboxed machine code for each concrete instantiation, whereas a field declared with an abstract type like Real would force Julia to box every value and dispatch dynamically at each field access.
Cricket analogy: A parametric struct Point{T} is like a single scorecard template that works for both a T20 match (T = 20 overs) and a Test match (T = unlimited overs) — same structure, specialized per format, with each printed card being concretely one format, not a vague mix of both.
Prefer parametric fields (x::T) over abstract-typed fields (x::Real) whenever you control the struct definition — this is one of the single biggest, easiest performance wins in Julia because it lets the compiler generate specialized code per concrete type instead of falling back to boxed, dynamically dispatched field access.
Methods and Dispatch on Custom Types
Once a struct exists, you define behavior for it by writing ordinary functions whose argument types include that struct, such as area(c::Circle) = π * c.radius^2 — Julia's multiple dispatch then selects the most specific matching method at each call site based on the runtime types of all arguments, not just the first one as in classical single-dispatch object-oriented languages. This means you can later define area(r::Rectangle) or even overlap(c::Circle, r::Rectangle) as separate methods of the same generic function name, and Julia will route each call to the correct implementation automatically.
Cricket analogy: Multiple dispatch choosing overlap(c::Circle, r::Rectangle) based on both arguments is like an umpire's LBW decision depending on both the bowler's delivery type and the batsman's stance simultaneously, not just one factor in isolation — the ruling (method) is selected from the full combination.
Declaring a struct field with an abstract type, such as radius::Real instead of radius::Float64 (or a type parameter T), forces Julia to box the value and prevents the compiler from specializing field access — this is one of the most common accidental performance regressions in Julia code and shows up clearly as red 'Any' or abstract-type output in @code_warntype.
- struct declares an immutable composite type by default; mutable struct allows field reassignment.
- Julia auto-generates a positional constructor; inner constructors (using new()) add validation, outer constructors add convenience.
- Parametric structs (struct Point{T} ... end) let one definition serve many concrete types efficiently.
- Concretely-typed fields let the compiler generate specialized, unboxed code; abstract-typed fields force boxing.
- Methods are defined outside the struct as ordinary functions dispatched on argument types.
- Multiple dispatch selects a method based on the runtime types of ALL arguments, not just the first.
- New methods for new types can be added later without modifying existing struct or method code.
Practice what you learned
1. By default, are Julia structs mutable or immutable?
2. What is the purpose of an inner constructor defined with new()?
3. Why is struct Point{T} x::T; y::T end generally faster than struct Point x::Real; y::Real end?
4. How does Julia decide which method of a generic function like area to call?
5. What is a key downside of a struct field declared as radius::Real instead of radius::Float64?
Was this page helpful?
You May Also Like
The Type System and Abstract Types
Understand Julia's single-rooted type hierarchy, how abstract types organize dispatch, how multiple dispatch selects methods, and how to diagnose type instability.
Arrays in Julia
Learn how Julia's built-in Array type stores, indexes, and mutates collections of data, and why column-major memory layout matters for performance.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics