How to use @kwdef
with parametric structs and default constructors?
I’m trying to understand how to best use @kwdef
with parametric structs in Julia, particularly when I want to define some default constructors and have those types nested in other custom types eventually.
Here’s an example. I’m defining some AbstractThing
types, and then composing them into a larger struct A_bigger_thing{T}
. I want to use @kwdef
to provide defaults, but I’m not sure I’m doing this in the right way.
MWE
abstract type AbstractThing end
@kwdef struct Thing1 <: AbstractThing
x::Float64 = 0.0
end
@kwdef struct Thing2 <: AbstractThing
x::Float64 = 0.0
y::Float64 = 1.0
end
# If I define this with @kwdef, I run into problems
# So I leave @kwdef off and write a manual outer constructor
mutable struct A_bigger_thing{T<:AbstractThing}
thing::T
value::Float64
end
# This works:
function A_bigger_thing{T}() where {T<:AbstractThing}
A_bigger_thing{T}(T(), 0.0)
end
# Example usage:
# julia> A_bigger_thing{Thing1}()
# A_bigger_thing{Thing1}(Thing1(0.0), 0.0)
# julia> A_bigger_thing{Thing2}()
# A_bigger_thing{Thing2}(Thing2(0.0, 1.0), 0.0)
All this is fine, but I’m getting confused a little when I start to use @kwdef
for convenience (in my real application there are 15 other fields that I provide defaults in the type definition).
For example, if I define Another_bigger_thing
using @kwdef
:
@kwdef mutable struct Another_bigger_thing{T<:AbstractThing}
thing::T
value::Float64 = 1.0
end
I can instantiate them like:
julia> Another_bigger_thing(thing = Thing1())
Another_bigger_thing{Thing1}(Thing1(0.0), 1.0)
julia> Another_bigger_thing(thing = Thing2())
Another_bigger_thing{Thing2}(Thing2(0.0, 1.0), 1.0)
But what I’d like is to be able to write is: Another_bigger_thing{Thing1}()
because I want this to be a field in another struct, for example:
@kwdef mutable struct An_even_bigger_thing{T<:AbstractThing}
value1::Float64 = 0.0
value2::Float64 = 1.0
big_thing::Another_bigger_thing{T} = Another_bigger_thing{T}()
end
So my approach was to define:
function Another_bigger_thing{T}() where {T<:AbstractThing}
Another_bigger_thing{T}(; thing = T())
end
My Question
This seems to work, but I’m wondering:
- Is this a good way to combine
@kwdef
with parametric structs and default constructors? - Is there a better way to support default construction (e.g.,
Another_bigger_thing{T}()
) while still using keyword defaults via@kwdef
? - Are there any subtle pitfalls to this approach when used in larger codebases?
Thanks in advance for any guidance or best practices here!