Union{T,Nothing} vs multiple dispatch

For performance reasons, it is advised to avoid Unions. But on the topic of Nothing, the documentation suggests the compiler will emit efficient code. Is it preferred to use multiple dispatch, i.e., have two versions of the function, one with the argument and one without, or to use Union{T,Nothing} and perform an explicit check, e.g.,

if T === nothing

A function with no argument is not the same as a function which receives nothing as an argument:

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

julia> f(x) = 2
f (generic function with 2 methods)

julia> f()
1

julia> f(nothing)
2

So that is not exactly the question. But yes, you can use if x == nothing and that will produce efficient code.

julia> function g(x)
           if x == nothing
               return 1
           else
               return 2
           end
       end
g (generic function with 1 method)

julia> @code_llvm g(nothing)
;  @ REPL[10]:1 within `g`
define i64 @julia_g_489() #0 {
top:
;  @ REPL[10]:3 within `g`
  ret i64 1
}

julia> @code_llvm g(1)
;  @ REPL[10]:1 within `g`
define i64 @julia_g_491(i64 signext %0) #0 {
top:
;  @ REPL[10]:5 within `g`
  ret i64 2
}

(there are not branches in the function - but that is not quite specific of Nothing, if the branch can be inferred from the input type, the compiler can very well create methods that avoid the branching for each type).

1 Like

What is preferred: to have two methods that dispatch on the type or one signature that uses a Union and tests for nothing?

Note: you want === not ==, since arbitrary things could be == nothing but only nothing is === nothing

2 Likes

I would personally prefer the two-signature version.

1 Like

Reason being? stylistically or performance? I’ve seen examples of both approaches in Base.

Stylistic. In this case, perf is clealry the same.

1 Like

Yeah, VSCode linters that and I change it to isnothing(x)

Ps. What can be nothing without being Nothing?

1 Like

Well, == is user-defined, so anything:

julia> struct MyType
         x::Int
       end

# This is probably a bad idea, but there's no reason you *can't* do it
julia> Base.:(==)(m::MyType, ::Nothing) = true

julia> MyType(1) == nothing
true

But === is always a test for actual equivalence, so even weird user code can’t override it:

julia> MyType(1) === nothing
false
2 Likes