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.@dynamicMacro
@dynamic [mutable] struct ... end

Define 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.show method that reflects this. These are only present if the block is free of any non-field expressions, such as custom constructors.

source

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 DynamicStructs
julia> @dynamic struct GPU model::String year::Int end
julia> 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 REPL Instance 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 = 2626
julia> 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)true
julia> 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.hasproperty
  • Base.propertynames
  • Base.getproperty
  • Base.setproperty!
  • Base.delete!
  • Base.hash
  • Base.:(==)
  • 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_hasproperty
  • DynamicStructs.dynamic_propertynames
  • DynamicStructs.dynamic_getproperty
  • DynamicStructs.dynamic_setproperty!
  • DynamicStructs.dynamic_delete!
  • DynamicStructs.dynamic_hash
  • DynamicStructs.dynamic_equality
  • DynamicStructs.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