# Why don't error messages name offending variables/function returns?

**URL:** <https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602>\
**Category:** Internals & Design\
**Created:** [June 13, 2026, 2:23pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602 "2026-06-13T14:23:41Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![ducksoverip](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ducksoverip/32/31967_2.png) [@ducksoverip](https://discourse.julialang.org/u/ducksoverip)\
**Post date:** [June 13, 2026, 2:23pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/1 "2026-06-13T14:23:42Z")

</div>

While I’m generally loving the experience of learning and using Julia, a frequent source of frustration is parsing its error messages. Even when I understand the content of the error message and I’m given the offending line, it can still be hard to actually nail down the source of the error because I’m not told what within the line actually caused the offense. Ideally a type conversion error would name the variable that had the wrong type. An array bounds error would name the array I was trying to access incorrectly, and so on.

As an example, I once wracked my brain for half a day trying to figure out why a line of arithmetic was trying to convert an object of type `Nothing` to `Float64`, but with 6 variables in the line and no guidance I ended up just using printing `typeof()` statements until I figured out that I accidentally had a function call return `Nothing` instead of the intended result. If it had simply said `Can't convert my_function(args) of return type Nothing to Float64`, I would immediately have known that the issue was with the function result.

Perhaps there’s a limitation of the language here that I’m fundamentally misunderstanding, but I guess just don’t understand why, if Julia can trace errors through a call stack to the specific line of source code, it can’t go one step further and just name the variable or function that’s the problem. If there’s a more nuanced explanation here, please do enlighten me.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 13, 2026, 3:12pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/2 "2026-06-13T15:12:52Z")

</div>

The conversion methods that throw the errors do not know anything about the call sites to provide the information you want, and the stack trace does not bother to infer the types of variables or other subexpressions in the offending lines. It’s possible for the call site to throw the error instead, but you’d have to know which variables could have the wrong types to begin with and throw the errors yourself. You can instead use type inference reflection e.g. `@code_warntype` to spot statically knowable types of subexpressions, though it may not stand out. For example, you’d have to notice the possible `+(::Nothing, Float64)` at the end because `@code_warntype` isn’t concerned about thrown errors and missing methods.

```julia-auto
julia> foo(x::Float64) = x + ifelse(rand((true, false)), 1.0, nothing)
foo (generic function with 1 method)

julia> @code_warntype foo(1.2)
MethodInstance for foo(::Float64)
  from foo(x::Float64) @ Main REPL[14]:1
Arguments
  #self#::Core.Const(Main.foo)
  x::Float64
Body::Float64
1 ─ %1 = Main.:+::Core.Const(+)
│ %2 = Main.ifelse::Core.Const(ifelse)
│ %3 = Main.rand::Core.Const(rand)
│ %4 = Core.tuple(true, false)::Core.Const((true, false))
│ %5 = (%3)(%4)::Bool
│ %6 = Main.nothing::Core.Const(nothing)
│ %7 = (%2)(%5, 1.0, %6)::Union{Nothing, Float64}
│ %8 = (%1)(x, %7)::Float64
└── return %8

```

It is however possible for reflection to sound alarms about statically known errors:

```julia-auto
julia> using JET

julia> @report_opt foo(1.2) # spots statically unknown types
No errors detected

julia> @report_call foo(1.2) # looks for more errors from statically known types
═════ 1 possible error found ═════
┌ foo(x::Float64) @ Main ./REPL[14]:1
│ no matching method found `+(::Float64, ::Nothing)` (1/2 union split): (x::Float64 + ifelse(rand(tuple(true, false)::Tuple{Bool, Bool})::Bool, 1.0, nothing)::Union{Nothing, Float64})
└────────────────────

```

You might be expecting something more obvious like ahead-of-time compiler errors for statically typed languages. Dynamically typed languages with REPLs don’t naturally do that, types don’t need to be known at compile-time, and type errors are allowed to be thrown at runtime like other errors. Although technically possible, runtime errors won’t rerun type inference at every method just to highlight parts of lines.

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [June 13, 2026, 3:49pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/3 "2026-06-13T15:49:26Z")

</div>

Try [JETLS.jl](https://github.com/aviatesk/JETLS.jl). It will warn you if variables can be nothing. If you start using it, it will probably give way too many warnings, but if you fix them one by one (or suppress invalid warnings), you get a much better experience in the end. AI can help to fix JETLS warnings, too.

---

<div class="post-metadata">

**Author:** ![ducksoverip](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ducksoverip/32/31967_2.png) [@ducksoverip](https://discourse.julialang.org/u/ducksoverip)\
**Post date:** [June 13, 2026, 6:37pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/4 "2026-06-13T18:37:18Z")

</div>

I was actually asking about detailed runtime error info. Basically, say I have an error like this:

```julia-auto
julia> a = zeros(Int64, 5)
5-element Vector{Int64}:
 0
 0
 0
 0
 0

julia> b = "silly string"
"silly string"

julia> for i in eachindex(a)
           a[i] = b
       end
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
The function `convert` exists, but no method is defined for this combination of argument types.

```

Why can’t the `MethodError` say something like:  
`object 'b' has type String, which cannot be converted to type Int64 in the assignment statement at REPL[3]:2.`

That carries the same info (no method exists to convert a string to an integer) but would also specifically name the variable that’s causing the issue. Likewise, given the following:

```julia-auto
julia> a = zeros(Int64, 5);

julia> b = ones(Int64, 50);

julia> for i in 1:50
           a[i] = b[i]
       end
ERROR: BoundsError: attempt to access 5-element Vector{Int64} at index [6]

```

Why can’t the `BoundsError` say `attempt to access 5-element Vector{Int64} 'a' at index [6]`? Then I would immediately know that the array access issue was in `a`, not `b`. Perhaps that’s what you were referring to when you said that

> [@Benny](#):
>
> The conversion methods that throw the errors do not know anything about the call sites to provide the information you want, and the stack trace does not bother to infer the types of variables or other subexpressions in the offending lines.

but I guess I don’t understand what you mean if that’s the case. Surely if it a) knows the objects associated with given variable names in the source code, b) knows the type of those objects at runtime, and c) can throw errors due to invalid operations on those objects, then it should be trivial to print the name(s) of the object(s) that caused the error, right?

I apologize, as I’m genuinely not trying to be rude or dense, but I really don’t understand why this is difficult to accomplish.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 13, 2026, 7:03pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/5 "2026-06-13T19:03:45Z")

</div>

> [@ducksoverip](#):
>
> Surely if it a) knows the objects associated with given variable names in the source code

It doesn’t. The call `f(a, b)` does not inform the method of `f` that the input objects were assigned to `a` and `b` in the call scope, so whatever error `f` throws cannot name those variables. In many cases there wouldn’t be variables to name, like `f(arr[42], 0)`. If the error is thrown by a nested function call, then the subexpressions would have to be traced across calls.

We’d know the subexpressions after some investigation, and hypothetically the program could know it too if it performed type inference or other static analyses across the stack trace when errors are thrown, but that kind of overhead is undesirable at runtime, especially if it could routinely happen in `try`-`catch`. It’s better to reserve static analyses for the statically knowable errors, no need to execute a possibly long program (though unit testing is still recommended to catch things static analysis can’t); I don’t know of anything that traces variable names like you want, though.

---

<div class="post-metadata">

**Author:** ![ducksoverip](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ducksoverip/32/31967_2.png) [@ducksoverip](https://discourse.julialang.org/u/ducksoverip)\
**Post date:** [June 13, 2026, 8:25pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/6 "2026-06-13T20:25:15Z")

</div>

Thanks for explaining in more detail—I really do appreciate it. If a program halts when an error is thrown, however, why does the overhead of performing type inference across the stack matter? I get that this would be a problem if it were occurring at every function call in advance of an error being thrown, but if it happened _after_ the error (like an implicit `finally`), then presumably the cost would be comparatively low.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 13, 2026, 10:47pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/7 "2026-06-13T22:47:13Z")

</div>

Ideally we should try to show the offending line start + column start + column end + line end, not just line start.

And then display the offending lines, + 4 lines context, with the offending substring highlighted.

I fear doing that would be quite a bit of work, because afaiu a lot of our debug info is on the “filename + linenumber only” level.

That is a problem that julia shares with a lot of other languages. For example, java tooling is abysmal with that respect (oh, nullpointer exception on line xyz. Yeah, which of these? Am I supposed to reformat my code because you can’t include column numbers? In an age where I can’t buy 4:3 monitors any longer?)

I don’t think we at julia have a real excuse for that issue. Java at least has the excuse of “in the 90’s, paying for column number in debug info was expensive”.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 14, 2026, 6:26am UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/8 "2026-06-14T06:26:24Z")

</div>

> [@ducksoverip](#):
>
> If a program halts when an error is thrown, however, why does the overhead of performing type inference across the stack matter?

I was hoping to get away with a sentence, but to dive deeper, you are making many assumptions here that makes your proposal convenient for your particular half-day issue and type errors in similarly narrow situations, not generally.

The vast majority of thrown errors aren’t elucidated by type inference in any way, e.g. `1÷0` throwing `DivideError`. Most `convert` problems throw `MethodError: Cannot `convert` `, but that’s not nearly the only one. Conditional `convert`s throw different errors, like `convert(Int, 1.2)` throwing `InexactError`. In the earlier `foo(1.2)` example, the type error doesn’t occur at `convert`, so we get a different `MethodError` message. Assuming we manage to figure out the exact subset of errors worth doing type inference for, it may fail anyway because a dynamic language does not demand these types be statically knowable.

So let’s further assume we have a different analysis, something along the lines of tracking the runtime values’ subexpressions and types across calls. There are many scenarios where throwing an error doesn’t halt the program; `catch` obviously, but also non-`wait`ed `Task`s. If I want to run a 3 hour program and take a generous lunch break for example, I’m very likely to let the program handle and perhaps log any errors that occur e.g. a failed HTTP request. Even if the analysis appears fast, a blink of an eye is massive overhead in computation that shouldn’t be repeated for arbitrarily many runtime errors. If these errors are actual bugs instead of informative failures, I don’t want to run a 3 hour program just to find them, especially if the program keeps halting at points I haven’t managed to handle and needs to be rerun from scratch to reach the next bug. For good reason, static analyses occur ahead of (execution) time on our schedule. As mentioned earlier, this comes more naturally to AOT-compiled statically typed languages.

We also don’t want to only run static analyses after a runtime error because there are plenty of potential bugs that don’t inform the program to throw an error. A minor change to your program could easily run to completion, only to return a result riddled with `missing`. `1/0 == Inf` in the floating point standard, so no `DivideError`. There may not be static analyses readily available to catch these even when statically known, so proactive handling goes a long way e.g. `ismissing`, `iszero`, `skipmissing`, `filter`.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 14, 2026, 7:01am UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/9 "2026-06-14T07:01:10Z")

</div>

> [@foobar\_lv2](#):
>
> Ideally we should try to show the offending line start + column start + column end + line end, not just line start.
> 
> And then display the offending lines, + 4 lines context, with the offending substring highlighted.

Could start here for more information on narrowing down the location of erroring calls in source: [finer-grained location info for error messages, strack traces: line number, column number range · Issue #53090 · JuliaLang/julia](https://github.com/JuliaLang/julia/issues/53090). I don’t think it would often tell us where bugs are by itself, but narrowing things down from the start makes it easier.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [June 15, 2026, 3:57am UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/10 "2026-06-15T03:57:09Z")

</div>

I think there’s a short answer to the question here, that is we need JuliaLowering.jl to land to make this possible. That’s currently in-flight, i.e. not possible today but hopefully soon.

---

<div class="post-metadata">

**Author:** ![pfitzseb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pfitzseb/32/45566_2.png) [@pfitzseb](https://discourse.julialang.org/u/pfitzseb)\
**Post date:** [June 15, 2026, 8:03am UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/11 "2026-06-15T08:03:10Z")

</div>

> <https://github.com/JuliaLang/julia/pull/62024>
>
> Atop #61991. With the plumbing work done (getting some of JuliaLowering's prove…nance through DebugInfo), we can print byte numbers in stack traces with little extra effort:
> 
> \<img width="820" height="359" alt="a" src="https://github.com/user-attachments/assets/fafb25a2-8c14-4997-897a-893b091fb424" /\>
> 
> There's more information we can print that I'm leaving out for now. For example, with a small patch to \`Base.print\_stackframe\`, we can print the source code too.
> \<details\>
> 
> \<summary\>patch\</summary\>
> 
> \`\`\`
> @eval Base begin
> # Print a stack frame where the module color is set manually with \`modulecolor\`.
> function print\_stackframe(io, i, frame::StackFrame, ndigits\_max::Int, max\_nested\_cycles::Int, nactive\_cycles::Int, ncycle\_starts::Int, modulecolor; prefix = nothing)
> file = string(frame.file)
> fl = StackTraces.frame\_location(frame)
> 
> # Used by the REPL to make it possible to open
> # the location of a stackframe/method in the editor.
> if haskey(io, :last\_shown\_line\_infos)
> push!(io\[:last\_shown\_line\_infos\], (string(frame.file), frame.line))
> end
> 
> inlined = getfield(frame, :inlined)
> modul = parentmodule(frame)
> 
> digit\_align\_width = ndigits\_max + 2 + max\_nested\_cycles - nactive\_cycles
> 
> # repeated section bracket line 1
> prefix === nothing || print(io, prefix)
> print(io, " ")
> printstyled(io, "├" ^ (nactive\_cycles - ncycle\_starts); color = :light\_black)
> printstyled(io, "┌" ^ ncycle\_starts; color = :light\_black)
> 
> # frame number
> print(io, lpad("\[" \* string(i) \* "\]", digit\_align\_width))
> print(io, " ")
> 
> # func name and arguments
> StackTraces.show\_spec\_linfo(IOContext(io, :backtrace=\>true), frame)
> println(io)
> 
> # repeated section bracket line 2
> prefix === nothing || print(io, prefix)
> print(io, " ")
> printstyled(io, "│" ^ nactive\_cycles; color = :light\_black)
> 
> # @ Module path / file : line
> print\_module\_path\_file(
> io, modul, file, fl.line, fl.col;
> modulecolor, digit\_align\_width = digit\_align\_width - 1)
> 
> # inlined
> printstyled(io, inlined ? " \[inlined\]" : "", color = :light\_black)
> 
> if fl.col != 0 && !isempty(file) && !frame.from\_c
> try
> open(file) do fd
> seek(fd, fl.byte-fl.col)
> source\_str = String(readline(fd))
> print(io, "\\n")
> printstyled(io, " " ^ digit\_align\_width \* "|", color = :light\_black)
> printstyled(io, source\_str; color=:light\_black)
> print(io, "\\n")
> printstyled(io, " " ^ digit\_align\_width \* "|", color = :light\_black)
> for i in eachindex(source\_str)
> printstyled(io, 1 \<= i - (fl.col-1) \<= (fl.byte\_end+1) - fl.byte ? '^' : ' '; color=:red)
> end
> end
> catch e
> end
> end
> end
> end
> \`\`\`
> \</details\>
> 
> \<img width="820" height="455" alt="b" src="https://github.com/user-attachments/assets/00d2fcb4-93bd-4ba2-ab7f-1c32a1dbd3c9" /\>
> 
> 
> We'll probably want a more reliable way of fetching source code before implementing that (and before fixing the byte-column != character-column issue).

---

<div class="post-metadata">

**Author:** ![ducksoverip](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ducksoverip/32/31967_2.png) [@ducksoverip](https://discourse.julialang.org/u/ducksoverip)\
**Post date:** [June 15, 2026, 1:27pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/12 "2026-06-15T13:27:16Z")

</div>

Thanks again for taking the time to explain things so thoroughly. I clearly didn’t have a correct picture of how things work under the hood. I’m still adjusting to the fact that Julia isn’t simply “a compiled language with a REPL”, even though it sometimes feels that way.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 15, 2026, 10:01pm UTC](https://discourse.julialang.org/t/why-dont-error-messages-name-offending-variables-function-returns/137602/13 "2026-06-15T22:01:46Z")

</div>

The reference implementation of Julia _is_ a compiled language (to native, I’m excluding bytecode interpreters) with a REPL, it’s just that “compiled language” strictly means a lot less than its colloquial categorization of mainstream languages. There are many more facets to language design, and while Julia does share some characteristics with mainstream AOT-compiled languages to make compilation worth doing at all, it’s still a dynamic language. Allowing more runtime errors and making static analysis harder comes with that. A few mainstream AOT-compiled languages _do_ have REPLs that maintain a persistent program context, it’s just less common to avoid or work around conflicting characteristics, and some crowds don’t demand a REPL workflow anyway.
