Dynamic Structs
Dynamic structs in DynamicStructs.jl extend Julia's standard structs with the ability to add, modify, and delete properties at runtime. This provides flexibility similar to what you might find in dynamic languages while maintaining the type safety and performance benefits of Julia's standard structs.
See Performance for details on performance characteristics and optimization tips, and Utilities for utilities to work with dynamic properties.
DynamicStructs.@dynamic — Macro@dynamic [mutable] struct ... endDefine a dynamic struct:
Instances of dynamic structs have dynamic properties that can be added/deleted at runtime. These properties are similar to
Any-typed fields of structs in terms of performance.Fields remain statically typed and accessing them compiles similarly to structs, so performance should not be significantly affected.
The macro adds a hidden field for storing dynamic properties with lazy initialization, meaning that the underlying storage is not allocated until the first dynamic property is added.
The default constructors of dynamic structs accept keyword arguments for dynamic properties, with a
Base.showmethod that reflects this. These are only present if the block is free of any non-field expressions, such as custom constructors.
DynamicStructs.isdynamictype — Functionisdynamictype(T)Check if T is a dynamic type.
DynamicStructs.isdynamic — Functionisdynamic(x)Check if x is an instance of a dynamic type.
Core Concepts
Dynamic structs combine static fields with dynamically added properties:
- Static fields: Defined at compile time, type-stable, and efficient
- Dynamic properties: Added at runtime, perform like
Any-typed fields
Creating Dynamic Structs
Use the @dynamic macro to create a dynamic struct:
julia> using DynamicStructsjulia> @dynamic struct GPU model::String year::Int endjulia> gpu = GPU("RTX 6000 Ada", 2022, hourly_cost = 0.18)Main.GPU("RTX 6000 Ada", 2022; hourly_cost = 0.18)julia> Docs.getdoc(gpu) # or ?gpu in a real REPLInstance of dynamic struct Main.GPU. Fields ≡≡≡≡≡≡ model :: String year :: Int64 Dynamic properties ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡ hourly_cost :: Any
You can also create mutable dynamic structs:
julia> @dynamic mutable struct Person name::String age::Int end
Working with Dynamic Properties
Adding and Accessing Properties
julia> p = Person("Bob", 25, hobby="Reading", location="New York")Main.Person("Bob", 25; hobby = "Reading", location = "New York")julia> p.skills = ["Programming", "Design"];julia> p.name # Static field"Bob"julia> p.hobby # Dynamic property"Reading"
Modifying Properties
julia> p.hobby = "Gaming""Gaming"julia> p.age = 2626julia> pMain.Person("Bob", 26; hobby = "Gaming", location = "New York", skills = ["Programming", "Design"])
Removing Properties
julia> delete!(p, :hobby)Main.Person("Bob", 26; location = "New York", skills = ["Programming", "Design"])julia> hasproperty(p, :hobby)false
Reflection
Check if a type is dynamic or if an instance is of a dynamic type:
julia> isdynamictype(Person)truejulia> isdynamic(p)true
Implementation Details
Dynamic structs include a private __properties field that stores all dynamic properties in a dictionary-like structure. This field is lazily initialized, meaning no storage is allocated until the first dynamic property is added.
The default constructors of dynamic structs accept keyword arguments for dynamic properties, and a custom Base.show method provides a clean representation.
Overwriting Dynamic Methods
The @dynamic macro defines methods for the following functions:
Base.haspropertyBase.propertynamesBase.getpropertyBase.setproperty!Base.delete!Base.hashBase.:(==)Base.Docs.getdoc
This prevents you from defining your own methods without disabling file precompilation (through __precompile__(false)).
Each of the functions above redirect to their respective generic functions in DynamicStructs:
DynamicStructs.dynamic_haspropertyDynamicStructs.dynamic_propertynamesDynamicStructs.dynamic_getpropertyDynamicStructs.dynamic_setproperty!DynamicStructs.dynamic_delete!DynamicStructs.dynamic_hashDynamicStructs.dynamic_equalityDynamicStructs.dynamic_getdoc
These are specifically defined for dynamic structs, and may be used to inherit the default behaviors.
Let us demonstrate with a silly example:
__precompile__(false) # put in same file
@dynamic struct AddOne end
function Base.getproperty(x::AddOne, name::Symbol)
DynamicStructs.dynamic_getproperty(x, name) + 1
end