What is the best way to require exactly 1 of 2 keyword arguments when writing a function?

FWIW, the code I posted doesn’t actually cause any cost at run-time because the compiler is smart enough to figure out what will happen just from the types of the arguments. Check out this example:

julia> function foo(x)
         if x === nothing
           return "hello world"
         else
           return 1.0
         end
       end
foo (generic function with 1 method)

This looks like it’s type-unstable because it could either return a String or a Float64 depending on the value of x. But the compiler is smart enough to know that the only case for which x === nothing holds is if x is of type Nothing. So it can actually figure out which branch of the if statement will be taken just from the type of the argument, and no checks have to happen at run-time. For example:

julia> @code_warntype foo(nothing)
Variables
  #self#::Core.Compiler.Const(foo, false)
  x::Core.Compiler.Const(nothing, false)

Body::String
1 ─ %1 = (x === Main.nothing)::Core.Compiler.Const(true, false)
│        %1
└──      return "hello world"
2 ─      Core.Compiler.Const(:(return 1.0), false)
julia> @code_warntype foo(2)
Variables
  #self#::Core.Compiler.Const(foo, false)
  x::Int64

Body::Float64
1 ─ %1 = (x === Main.nothing)::Core.Compiler.Const(false, false)
└──      goto #3 if not %1
2 ─      Core.Compiler.Const(:(return "hello world"), false)
3 ┄      return 1.0

The if statement has been removed at compile time, and there’s no actual type instability.

This is a pretty convenient pattern in Julia: just write out the code in a straightforward obvious way, including using run-time checks if that’s convenient. You can use @code_warntype to check for type instabilities and @btime (from BenchmarkTools) to check for performance problems and only go to more complicated type manipulation if it’s actually necessary.

3 Likes