How to make a macro for optional struct fields and function parameters?

Dear all,

Would it be a simple way to code this:

struct MyStruct
   a::Int
   IF OPTION THEN
       b::Int
   END IF
   new MyStruct(
       a_val::Int,
       IF OPTION THEN
           b::Int
       END IF
    )
end

I’m struggling with the syntax of macros, any hint ?

Thanks a lot,
Regards,

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.

1 Like

Thanks for your answer. I indeed want a compile-time option. Would you know how to make it ?

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.

Many thanks,
Have a nice day,

1 Like

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

Thanks a lot. Nice example to help me with macros.

1 Like