Exception subtypes in Base



As I was implementing an Exception subtype for the first time, I looked at some implementations in Base, for example ArgumentError. I was surprised by two things:

  • a lot of the subtypes are mutable
  • a lot of the subtypes don’t have concretely typed fields

On the other hand, the documentation refers to UndefVarError as an example of how to write a custom exception type, which is an immutable struct without abstract fields.

Just wondering: why is this? Also: does this have anything to do with the practice of throwing in a separate, @noinline'd function to achieve maximal performance? Ref e.g.: https://github.com/JuliaLang/julia/pull/23088#issuecomment-320279238.


mutable is probably just there for historical reason. You don’t need to type the field though. It’s just and error and there’s almost no reason to type the field.


I think the reason is that performance does not matter a lot since exceptions are only used in slow paths anyway, and as long as they are thrown from a separate @noinline function they does not slow the common path (as you noticed). But making them immutable would likely be a good idea. OTC, adding type parameters for fields is a bit more invasive and would slow down compilation by specializing on these types, so it might not be worth it.


Thanks. I was just wondering if not concretely typing everything could make the non-throwing path slightly slower as well, but it seems that that is not the case. For:

struct ExceptionA <: Exception

mutable struct ExceptionB <: Exception

@noinline function a(x)
    x <= 0 || throw(ExceptionA("x > 0"))

@noinline function b(x)
    x <= 0 || throw(ExceptionB("x > 0"))

@noinline throw_a() = throw(ExceptionA("x > 0"))
@noinline function a2(x)
    x <= 0 || throw_a()

a and b have the same code_llvm, and a2 is slightly faster than a and b, so the result from https://github.com/JuliaLang/julia/pull/23088#issuecomment-320279238 holds.


See also https://github.com/JuliaLang/julia/pull/23469