# Nothing and type stability in kwargs

**URL:** https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532
**Category:** New to Julia
**Tags:** dispatch, kwargs, keyword-arguments
**Created:** [December 6, 2024, 11:10am UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532 "2024-12-06T11:10:40Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![kockahonza](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@kockahonza](https://discourse.julialang.org/u/kockahonza)
#### Post date: [December 6, 2024, 11:10am UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/1 "2024-12-06T11:10:40Z")

</div>

Hi, so this is a fairly general question, but is it possible to use Nothing without type stability related performance hits? A specific example I have now is a simple simulator where I want to implement multiple optional exit conditions, say:

```julia
function run_sim(...; exit_steps=nothing, exit_time=nothing)
    exit = false
    sim_steps = 0
    sim_time = 0.0
    while !exit
        # do some simulation step, e.g.
        sim_time += rand()
        sim_steps += 1
        # check exit conditions
        if !isnothing(exit_time)
            if sim_time >= exit_time
                @info "Exited due to max simulation time met"
                exit = true
            end
        end
        if !isnothing(exit_steps)
            if sim_steps >= exit_steps
                @info "Exited due to max simulation number of steps met"
                exit = true
            end
        end
    end
end

```

but here `exit_steps` and `exit_time` can be either `nothing` which means no exit condition or a value.

Now as far as I understand if they were arguments then julia could specialize on their type when the function is called I hope even eliminate the `!isnothing` calls? But can this be done with kwargs instead? I’d prefer that as having fixed argument positions for a potentially quite long list of different conditions can be a pain to deal with.

One “solution” I can think of is to use Val to force some compile time optimizations but I get the feeling that that’s not really a very good way to deal with problems. Another thing I can think of is making a struct which contains all the conditions together and then pass that as an argument (more manageable) but if I understand things correctly that struct would still need to be heavily parametrized to carry all that type information. I suspect there may be a better solution to this.

---

<div class="post-metadata">

### Author: ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)
#### Post date: [December 6, 2024, 11:22am UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/2 "2024-12-06T11:22:06Z")

</div>

The simplest solution is to split into one user facing function with keyword arguments and an implementation function with positional arguments.

```julia
function run(args...; exit_steps=nothing, exit_time=nothing)
    _run(args..., exit_steps, exit_time)
end

function _run(..., exit_steps, exit_time)
    ...
end

```

The drawback is obviously that this is annoying boilerplate stuff.

---

<div class="post-metadata">

### Author: ![barucden](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/barucden/32/26154_2.png) [@barucden](https://discourse.julialang.org/u/barucden)
#### Post date: [December 6, 2024, 11:48am UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/3 "2024-12-06T11:48:38Z")

</div>

There is an even simpler solution!

```julia
function run(x, y; exit_steps=typemax(Int64), exit_time=typemax(Int64))
    n_steps = 0
    n_seconds = ...
    while n_steps < exit_steps && n_seconds < exit_time
        n_steps += 1
        n_seconds = ...
    end
end

```

It cannot be used in all situations though.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [December 6, 2024, 1:40pm UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/4 "2024-12-06T13:40:47Z")

</div>

> [@kockahonza](#):
>
> But can this be done with kwargs instead?

Yes, Julia allows specializing on keyword argument type. Constant propagation should work fine, too. Otherwise, report a bug.

What makes keyword arguments different than positional arguments is that keyword arguments are not dispatched on (modulo bugs: [Keyword arguments affect methods dispatch · Issue #9498 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/9498)). So your code is fine.

That said, don’t overuse keyword arguments, as they add extra stack frames, which may cause bad performance in the presence of recursion or ugly stack traces in case of an error.

> [@kockahonza](#):
>
> `!isnothing(exit_steps)`

Stylistically, I prefer something like `exit_steps isa Number` or `exit_steps isa Real`.

---

<div class="post-metadata">

### Author: ![kockahonza](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@kockahonza](https://discourse.julialang.org/u/kockahonza)
#### Post date: [December 6, 2024, 4:36pm UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/5 "2024-12-06T16:36:45Z")

</div>

Thanks for the prompt replies!

@barucden I thought of using typemax but as you say it’s not general.

@GunnarFarneback That is true, I wanted to avoid too much boilerplate but realistically it isn’t actually that much.

@nsajko This is perfect, I didn’t realize though it does make sense, I confused the specialization vs dispatch thing! I’m glad to hear as I very much would like to rely on constant propagation and compile time optimizations. Thanks for the stack frame tip and as a side note I’m somewhat mixed about the stylistic point. Your version would make it clear that it is really checking the types which is lovely, but then if I was to pass a String or something it would just silently do nothing whereas I would prefer it to fail at that point.

Actually, I hope it’s fine if I add one more related question (still mostly fits under the title). When managing complex kwarg structures I often find myself setting the default to `nothing` in the function signature and then having a block at the start of the function such as

```julia
function f(; kw1=nothing)
    if isnothing(kw1)
        kw1 = "sensible_default"
    end
end

```

however, if I understand the term correctly this is also a type instability isn’t it? Given the nature of `Nothing` it seems to me like a reasonable one and so I hope that this comes without a performance cost. Does it? I’m not sure if Julia is smart enough to figure this out or how to test this.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [December 6, 2024, 5:21pm UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/6 "2024-12-06T17:21:26Z")

</div>

> [@kockahonza](#):
>
> Given the nature of `Nothing` it seems to me like a reasonable one and so I hope that this comes without a performance cost. Does it?

As long as the type of the argument is known to be `Nothing`, both the branch and the type instability should get optimized out. Additionally, even when the type is not concretely known, but it _is_ known to subtype a small-enough type `Union` (less than four types, to be specific, if I remember correctly), the performance should be fine because of the “union splitting” optimization that the compiler is often able to do.

> [@kockahonza](#):
>
> I’m not sure if Julia is smart enough to figure this out or how to test this.

The usual tools are:

- `@code_typed`, `code_typed`, `@code_warntype`, `code_warntype`, `@code_llvm`, `code_llvm`. Some of these are from the `Base` module, others are from the InteractiveUtils standard library package. All are available and loaded in an interactive REPL session, while a noninteractive REPL session might require loading InteractiveUtils first.
- `@descend`, `descend` from the Cthulhu.jl package
- `@report_call`, `report_call`, `@report_opt`, `report_opt` from the JET.jl package

Each of these has mostly the same interface, except that the macro versions try to be more user friendly by letting the user provide values instead of explicit types.

An example demonstrating the branch getting optimized out when the argument type `Nothing` is specialized on:

```julia-repl
julia> function f(a = nothing)
           if isnothing(a)
               a = 3
           end
           a
       end
f (generic function with 2 methods)

julia> code_typed(f, Tuple{}) # same as `@code_typed f()`
1-element Vector{Any}:
 CodeInfo(
1 ─ return 3
) => Int64

julia> function g(; a = nothing)
           if isnothing(a)
               a = 3
           end
           a
       end
g (generic function with 1 method)

julia> code_typed(g, Tuple{}) # same as `@code_typed g()`
1-element Vector{Any}:
 CodeInfo(
1 ─ return 3
) => Int64

```

The above also shows that there’s no difference between keyword (as in `g`) and positional (as in `f`) arguments.

An example to demonstrate the union-splitting optimization:

```julia-repl
julia> function f(collection)
           s = 0
           for e ∈ collection
               s += if e isa Number
                   e
               else
                   0
               end
           end
           s
       end
f (generic function with 1 method)

julia> code_typed(f, Tuple{Vector{Union{Nothing,Int}}}) # same as `@code_typed f(Union{Nothing,Int}[])`
1-element Vector{Any}:
 CodeInfo(
[...]
) => Int64

```

> [@kockahonza](#):
>
> I’m somewhat mixed about the stylistic point. Your version would make it clear that it is really checking the types which is lovely, but then if I was to pass a String or something it would just silently do nothing whereas I would prefer it to fail at that point.

FWIW one could use a dispatch constraint on the argument of the method to guard against that. It’s preferable to throw before the method gets called (due to dispatch) rather than in the body of the method, as that makes stack traces friendlier, and also might play better with reflection tools such as `isapplicable` and `hasmethod`. Using reflection is discouraged, though.

---

<div class="post-metadata">

### Author: ![kockahonza](https://avatars.discourse-cdn.com/v4/letter/k/ecc23a/32.png) [@kockahonza](https://discourse.julialang.org/u/kockahonza)
#### Post date: [December 11, 2024, 10:50am UTC](https://discourse.julialang.org/t/nothing-and-type-stability-in-kwargs/123532/7 "2024-12-11T10:50:29Z")

</div>

Perfect! I’m glad to hear and thanks for the in depth answers, examples and tips!! I’m happy that I mostly don’t need to change how I do things and I have started using Cthulhu which has been a great improvement and also shows some of the optimized statements as `Core.Const(true)` etc.
