Why can't Enums be #undef?

janneshb
Fri, 06 May 2022 19:07:34 +0000
General Usage

When trying to initialize Enum as #undef it instead goes into an invalid state by adopting an arbitrary integer.

Consider the following example:

julia> @enum Fruit Apple Banana Peach

julia> struct Basket{T}
           fruit::T

           function Basket{T}() where {T}
               return new{T}()
           end
       end

julia> fruit_basket = Basket{Fruit}()
Basket{Fruit}(Main.<invalid #1804537424>)

When initializing an empty struct like this, I would assume its members to be #undef or 0 until I set them. This works totally fine with other datatypes:

julia> float_basket = Basket{Float64}()
Basket{Float64}(0.0)

julia> string_basket = Basket{String}()
Basket{String}(#undef)

Integers are a bit of special case:

julia> int_basket = Basket{Int64}()
Basket{Int64}(140052098128304)

I guess Enums will be initialized the same way as integers. Therefore, they are assigned some random integer which in most cases will be invalid.

So, my first question would be:
Why aren’t Enums just set to 0, i.e. their first value, by default?

But, there is more to my problem.
With an Enum like above, one has to be careful to not use it before assigning a proper value to it.

However, when trying to use Distribtued, the invalid Enum just breaks everything.
We set up the following example to demonstrate the issue.

julia> using Distributed

julia> addprocs(2)
2-element Vector{Int64}:
 2
 3

julia> @everywhere @enum Fruit Apple Banana Peach

julia> @everywhere mutable struct Basket{T}
           fruit::T

           function Basket{T}() where {T}
               return new{T}()
           end
       end

julia> for i in 1:5
           Basket{Fruit}()
       end

julia> @distributed vcat for j in 1:5
           Basket{Fruit}()
       end
ERROR: TaskFailedException
Stacktrace:
 [1] wait
   @ ./task.jl:334 [inlined]
 [2] fetch
   @ ./task.jl:349 [inlined]
 [3] preduce(reducer::Function, f::Function, R::UnitRange{Int64})
   @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/macros.jl:274
 [4] top-level scope
   @ /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/macros.jl:286

    nested task error: ArgumentError: invalid value for Enum Fruit: 1273609008
    Stacktrace:
      [1] enum_argument_error(typename::Symbol, x::Int32)
        @ Base.Enums ./Enums.jl:85
      [2] Fruit
        @ ./Enums.jl:198 [inlined]
      [3] read(io::Sockets.TCPSocket, #unused#::Type{Fruit})
        @ Base.Enums ./Enums.jl:22
      [4] deserialize(s::Distributed.ClusterSerializer{Sockets.TCPSocket}, t::DataType)
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:1428
      [5] handle_deserialize(s::Distributed.ClusterSerializer{Sockets.TCPSocket}, b::Int32)
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:865
      [6] deserialize(s::Distributed.ClusterSerializer{Sockets.TCPSocket}, t::DataType)
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:1435
      [7] handle_deserialize(s::Distributed.ClusterSerializer{Sockets.TCPSocket}, b::Int32)
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:870
      [8] deserialize_fillarray!(A::Vector{Basket{Fruit}}, s::Distributed.ClusterSerializer{Sockets.TCPSocket})
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:1230
      [9] deserialize_array(s::Distributed.ClusterSerializer{Sockets.TCPSocket})
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:1222
     [10] handle_deserialize(s::Distributed.ClusterSerializer{Sockets.TCPSocket}, b::Int32)
        @ Serialization /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:852
     [11] deserialize
        @ /opt/julia-1.7.2/share/julia/stdlib/v1.7/Serialization/src/Serialization.jl:801 [inlined]
     [12] deserialize_msg(s::Distributed.ClusterSerializer{Sockets.TCPSocket})
        @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/messages.jl:87
     [13] #invokelatest#2
        @ ./essentials.jl:716 [inlined]
     [14] invokelatest
        @ ./essentials.jl:714 [inlined]
     [15] message_handler_loop(r_stream::Sockets.TCPSocket, w_stream::Sockets.TCPSocket, incoming::Bool)
        @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/process_messages.jl:169
     [16] process_tcp_streams(r_stream::Sockets.TCPSocket, w_stream::Sockets.TCPSocket, incoming::Bool)
        @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/process_messages.jl:126
     [17] (::Distributed.var"#99#100"{Sockets.TCPSocket, Sockets.TCPSocket, Bool})()
        @ Distributed ./task.jl:423
    Stacktrace:
     [1] remotecall_fetch(::Function, ::Distributed.Worker, ::Function, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
       @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:469
     [2] remotecall_fetch
       @ /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:461 [inlined]
     [3] #remotecall_fetch#158
       @ /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
     [4] remotecall_fetch
       @ /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/remotecall.jl:496 [inlined]
     [5] (::Distributed.var"#169#170"{typeof(vcat), var"#1#2", UnitRange{Int64}, Vector{UnitRange{Int64}}, Int64, Int64})()
       @ Distributed /opt/julia-1.7.2/share/julia/stdlib/v1.7/Distributed/src/macros.jl:270

julia>

The behavior is the same in Julia versions 1.7.2, 1.5.4 and 1.3.1.

Although this error makes sense when you know about the way Enums are assigned arbitrary values and the way data is transferred between distributed workers. However, in large project this is incredibly hard to track down.
Therefore, I once more want to ask: why aren’t Enums 0 by default, the same way Float64 are? Or, even better, why don’t they support #undef?

9 posts - 5 participants