You can’t do this in the sense of “one struct whose field list changes depending on a runtime OPTION.” In Julia, the set of fields is part of the type definition: a concrete type is final (you can’t subtype it), and a concrete DataType has a fixed size, storage layout, and (optionally) field names. That’s exactly why the compiler can lay it out efficiently and why instances of the same concrete type can’t have different fields.
So if the OPTION is only known at runtime, a macro can’t magically make a single struct definition grow/shrink fields per instance.
Two practical alternatives:
1) Define two concrete structs + a common abstract supertype, then dispatch
abstract type AbstractCfg end
struct CfgWithB <: AbstractCfg
a::Int
b::Int
end
struct CfgNoB <: AbstractCfg
a::Int
end
# Behavior via dispatch:
value(x::CfgWithB) = x.a + x.b
value(x::CfgNoB) = x.a
This keeps each concrete type’s layout fixed, but still lets you represent “with/without field” cleanly.
2) If you really want runtime-extensible fields, use a dict-like container
For “OPTIONfields” that are truly dynamic at runtime, model them as key-value data. If you want dot-access ergonomics, take a look at PropertyDicts.jl, which wraps an AbstractDict and provides getproperty/setproperty! for Symbol/String keys.
If OPTION is a constant (i.e., it will never change), then you can simply do something like
const OPTION = true # must be a constant (or at least a constant expression that `@static` can decide)
@static if OPTION
struct MyStruct
a::Int
b::Int
end
else
struct MyStruct
a::Int
end
end
In this setup, the definition of MyStruct is fixed at compile time.
However, even if the value is “known at compile time” in some broader sense, if OPTION could be either true or false depending on the build/environment (i.e., you want to support both variants), then you still can’t have a single concrete type whose field list changes dynamically. In that case, one practical approach is to keep the b field and make it optional by declaring its type as Union{T,Nothing}. You can store nothing to represent “b is absent.”
Many thanks for your help.
Regarding the option macro, if there a way to avoid repeating the common parts ?
I know that this approach has its drawbacks, but to learn how this works I would need to be able to do what I wanted to do at the beginning, that is to be able to generate through a macro the code of the structure.
Yes, you can fold the @static if OPTION branching into the macro. The macro can emit both struct definitions inside a @static if ... else ... end, and the compiler will keep only the selected branch at compile time. Here is an example:
macro def_optional_struct(name, option, common_block, opt_block)
return esc(quote
@static if $option
struct $name
$common_block
$opt_block
end
else
struct $name
$common_block
end
end
end)
end
# usage
const OPTION = true
@def_optional_struct 🍕 OPTION begin
🧀::Int
🥓::Float64
end begin
🍤::String
end
julia> dump(🍕)
struct 🍕 <: Any
🧀::Int64
🥓::Float64
🍤::String