Outer constructor method is failing

I don’t really understand why in the following code Payload(;a, b) = Payload(a=a, b=b, c=Float64[]) fails. It looks a legitimate expression to me.

module Test
Base.@kwdef mutable struct Payload
    a::Int
    b::String
    c::Vector{Float64}
end

Payload(;a, b) = Payload(a=a, b=b, c=Float64[])

@show Payload(a=1, b="apple")
end

WARNING: replacing module Test.
ERROR: MethodError: no method matching Main.Test.Payload(; a=1, b="apple", c=Float64[])
Closest candidates are:
  Main.Test.Payload(; a, b) at E:\work\julia\test2.jl:8 got unsupported keyword argument "c"        
  Main.Test.Payload(::Int64, ::String, ::Vector{Float64}) at E:\work\julia\test2.jl:3 got unsupported keyword arguments "a", "b", "c"
  Main.Test.Payload(::Any, ::Any, ::Any) at E:\work\julia\test2.jl:3 got unsupported keyword arguments "a", "b", "c"
Stacktrace:
 [1] kwerr(kw::NamedTuple{(:a, :b, :c), Tuple{Int64, String, Vector{Float64}}}, args::Type)
   @ Base .\error.jl:165
 [2] Main.Test.Payload(; a::Int64, b::String)
   @ Main.Test E:\work\julia\test2.jl:8
 [3] top-level scope
   @ show.jl:1045
 [4] eval
   @ .\boot.jl:368 [inlined]

This works fine:

module Test
Base.@kwdef mutable struct Payload
    a::Int
    b::String
    c::Vector{Float64}
end

Payload(;a, b) = Payload(a, b, Float64[])

@show Payload(a=1, b="apple")
end

Ah, it looks like that default keyword outer constructor method is not provided once the user defines one for himself.

module Test
Base.@kwdef mutable struct Payload
    a::Int
    b::String
    c::Vector{Float64}
end

@show methods(Payload)

Payload(;a, b) = Payload(a, b, Float64[])

@show methods(Payload)

@show Payload(a=1, b="apple")
end

WARNING: replacing module Test.
methods(Payload) = # 3 methods for type constructor:
[1] Main.Test.Payload(; a, b, c) in Main.Test at util.jl:489
[2] Main.Test.Payload(a::Int64, b::String, c::Vector{Float64}) in Main.Test at E:\work\julia\test2.jl:3
[3] Main.Test.Payload(a, b, c) in Main.Test at E:\work\julia\test2.jl:3
methods(Payload) = # 3 methods for type constructor:
[1] Main.Test.Payload(; a, b) in Main.Test at E:\work\julia\test2.jl:10
[2] Main.Test.Payload(a::Int64, b::String, c::Vector{Float64}) in Main.Test at E:\work\julia\test2.jl:3
[3] Main.Test.Payload(a, b, c) in Main.Test at E:\work\julia\test2.jl:3
Payload(a = 1, b = "apple") = Main.Test.Payload(1, "apple", Float64[])

Is this something similar in spirit to:

If any inner constructor method is defined, no default constructor method is provided: it is presumed that you
have supplied yourself with all the inner constructors you need.

No, it’s not that. But we don’t dispatch on kwargs, so when you define the (;a, b) constructor you overwrite the automatic (; a, b, c) constructor. They are both equivalent to a constructor with no arguments to dispatch.

The solution is to make c an optional kwarg.

2 Likes

Thank you so much for reminding me of we don’t dispatch on kwargs!