Type Instability and Union Splitting

So I know the following code is not type stable:

foo(x::Int64, y::Float64) = x < y ? x  : y

As a human I understand that the return type of this function must be Union{Int64, Float64}.

Does the compiler also understand this? In a function that calls foo will union splitting be used in order to make the code performant despite the type instability here?

On a related note I have noticed that Base.iterate is another function that falls under a similar boat. That is it returns a Union{T, Nothing}. Is this treated the same way, or is there something special about it?

Would explicitly declaring the return type as the appropriate Union help the compiler generate efficient code in either case, or is that information that the compiler determines for itself regardless?

Yes and yes.

1 Like

Typically, that’s unnecessary and doesn’t help. The compiler will figure this out by itself.

A useful tool for this is @code_warntype

julia> @code_warntype foo(1,2.0)
MethodInstance for foo(::Int64, ::Float64)
  from foo(x::Int64, y::Float64) in Main at REPL[1]:1
Arguments
  #self#::Core.Const(foo)
  x::Int64
  y::Float64
Body::Union{Float64, Int64}
1 ─ %1 = (x < y)::Bool
└──      goto #3 if not %1
2 ─      return x
3 ─      return y

The line Body::Union{Float64, Int64} indicates that Julia has determined the return type of this call to be the union you’d expect.

There are a few cases (but not this one) where @code_warntype will “lie” to you. It always assumes that code will fully specialize on the input types. This can lead it to indicate that things are inferred even when they may not be due to cases where Julia avoids specialization. There might be a couple of other situations where it also gives misleading results, but this is the main hiccup I’m aware of.

1 Like