Keyword argument constructor breaks Incomplete constructor

julia> mutable struct Foo
           x::Float64
           y::Float64

           Foo() = new()
           Foo(;x::Float64, y::Float64) = new(x, y)
       end

julia> Foo()
ERROR: UndefKeywordError: keyword argument x not assigned

Removing the keyword constructor allows calling the incomplete constructor.

julia> mutable struct Bar
           x::Float64
           y::Float64

           Bar() = new()
       end

julia> Bar()
Bar(6.94551551140594e-310, 5.0e-324)

Is there a way to have both the full keyword constructor and the incomplete constructor?

1 Like

Interesting! It works if the keyword constructor is defined first:

julia> mutable struct Foo
           x::Float64
           y::Float64

           Foo(;x::Float64, y::Float64) = new(x, y)
           Foo() = new()
       end

julia> Foo()
Foo(6.94551569230195e-310, 0.0)

Looks like this gotcha was already reported here:

1 Like

You just ovewriting that method. Cf

julia> f() = ()
f (generic function with 1 method)

julia> f(; a = 1, b = 2) = (a, b)
f (generic function with 1 method)

julia> methods(f) # NOTE: 1 method
# 1 method for generic function "f":
[1] f(; a, b) in Main at REPL[141]:1

julia> f()
(1, 2)

How could the language distinguish the first one from the second called without any keyword arguments?

I understand how your example with only optional keyword arguments is indistinguishable from no arguments.

But I expect the language to distinguish between a method that accepts zero arguments and a method that accepts two mandatory keyword arguments.

The language can distinguish between these methods, but only if they are defined in a special sequence. It’s confusing why methods still displays the same output, even though the method was either modified to accept different arguments or a new method was added. It would be helpful for methods to show whether the keyword arguments are optional or mandatory.

# ------ working sequence ----------

julia> f(; a, b) = a + b
f (generic function with 1 method)

julia> methods(f)
# 1 method for generic function "f":
[1] f(; a, b) in Main at REPL[1]:1

julia> f() = 5
f (generic function with 1 method)

# "overwritten" method looks the same as before, but now also supports zero arguments.

julia> methods(f)
# 1 method for generic function "f":
[1] f(; a, b) in Main at REPL[5]:1

# support for no-arguments and mandatory keyword arguments

julia> f()
5

julia> f(a=1, b=2)
3

# ------------ failing sequence ----------------

julia> g() = 5
g (generic function with 1 method)

julia> g(; a, b) = a + b
g (generic function with 1 method)

# adding support for mandatory keyword arguments afterwards breaks support for no arguments

julia> g()
ERROR: UndefKeywordError: keyword argument a not assigned
Stacktrace:
 [1] g() at ./REPL[10]:1
 [2] top-level scope at REPL[11]:1

I’ve not seen mandatory keyword arguments before, and it seems to me like they should just be positional arguments. Then you have no issues, yes?

I know this is not helpful for your question.

EDIT: Well I guess there were some methods (eg. sum) that did start forcing me to supply dims

I like to use mandatory keyword arguments instead of positional arguments to avoid bugs when calling constructors of large structs. It’s easy to mess-up the sequence of arguments, and there are no obvious errors if the swapped arguments share the same type. This also helps catch any bugs after adding/removing/reorganizing fields of a struct.

Are there other recommendations for avoiding these types of bugs?

Keyword arguments don’t participate in dispatch (on the surface, the internal implementation is another thing, see below).

This is a quirk of the implementation — you are overwriting one of the three functions mentioned there. It is a known issue, see

Just don’t rely on this.

1 Like