"Nullable" (really "Nothingable") Types

I’m curious if there’s a proper Julian way to implement optional struct fields through an effective Union{T, Nothing}.

I’ve written the macro below and it seems to work for my very simple test case. Is there a better way to achieve this? I suppose there could also be a macro for specific fields instead of the whole struct.

macro optional(block::Expr)
    struct_name = block.args[2]
    fields_expr = block.args[3].args
    fields = Expr[]
    for field ∈ fields_expr
        if typeof(field) !== LineNumberNode
            s = field.args[1]
            t = field.args[2]
            new_field = :($(s)::Union{$(t), Nothing})
            push!(fields, new_field)
        end
    end
    esc(quote
            struct $struct_name
                $(fields...)
            end
    end)
end

@optional struct A
    x::Float64
    y::Float64
end

a = A(1.0, nothing)
println(a)

One other option would be something like

julia> macro var"?"(T)
           esc(:(Union{$T, Nothing}))
       end
@? (macro with 1 method)

julia> struct A
           x:: @? Float64
           y:: Float64
       end

julia> f(x::@? A) = x
f (generic function with 1 method)

julia> f(nothing)

julia> f(A(nothing, 1))
A(nothing, 1.0)

Welp, I’m sold. That does sound like the best route! I didn’t even know one can use strings to define methods like you just did… Crazy stuff. Thanks!

One thing to note is that the var stringmacro was introduced in version 1.3, but it’s a oneliner to define your own version:

julia> macro myvar_str(s) esc(Symbol(s)) end;

julia> myvar"a multiword variable name" = 1
1

julia> myvar"a multiword variable name"
1