Default value of *some* fields in a mutable struct

What behavior do you want the non-initialized fields to have? Presumably to throw an error for any operation using them? Or do you want the non-initialized values to propagate and return other non-initialized values after operations?

This is the difference between nothing and missing in Julia. Since Julia aims to have both more “programming” and more “data science” uses, we don’t want to require a single default behavior for non-initialized values.

The Union{Nothing, Int64} or Union{Missing, Int64} is the correct solution to your problem, and it has the benefit of being explicit about the behavior of your uninitialized values.

@davidbp, great improvement!

I wrote a stub of the macro I mentioned:

macro awesome(s)
    if s.head !== :struct 
        error("Not a struct def")
    end 

    ismutable = s.args[1]
    if !ismutable
        error("Not mutable")
    end
    
    name = s.args[2]
    body = s.args[3]    

    ctor = :(function $(name)(;kwargs...)
                K = new()
                for (key, value) in kwargs
                    field_type_key = typeof(getfield(K,key))
                    setfield!(K, key, convert(field_type_key, value))
                end
                return K
            end)
            
    newbody = [body.args; ctor]
    
    return Expr(s.head, ismutable, name, Expr(body.head, newbody...))       
end

Now we can write:

@awesome mutable struct coly8
    x :: Int32
    y :: Float64
    z :: Bool
    t :: Int32
    u :: Float32
    v :: Bool
end
julia> coly8(u=4.0)
coly8(8, 9.200841125e-315, true, 0, 4.0f0, true)

The next step would be parsing a expression in a struct with predefined constants or use the x::Int = 42 syntax like in Parameters.jl. Truly limitless set of options :slight_smile: I would add also zeroing of not defined fields like most of languages do.

9 Likes

Sorry for reopening this thread, would you happen to know if one can get this to work for arrays? An example is taking your macro and struct and redefining v as such:

v :: Array{Float32,1}

Then afterwards if you try to use coly8(v=[2.f0;2.f0]), then the error becomes:

ERROR: UndefRefError: access to undefined reference
Stacktrace:
 [1] coly8(; kwargs::Base.Iterators.Pairs{Symbol,Array{Float32,1},Tuple{Symbol},NamedTuple{(:v,),Tuple{Array{Float32,1}}}}) at ..
 [2] top-level scope at none:1

Or maybe there is another way to do this in Julia today?

EDIT: Figured it out using Parameters.jl

Kind regards

2 Likes

You may also take a look at Base.@kwdef.
Here is an example:

  julia> Base.@kwdef struct Foo
             a::Int = 1         # specified default
             b::String          # required keyword
         end
  Foo

  julia> Foo(b="hi")
  Foo(1, "hi")

  julia> Foo()
  ERROR: UndefKeywordError: keyword argument b not assigned
3 Likes

Now the Base.@kwdef is exported and available in public API?

1 Like