Stream: helpdesk (published)

Topic: Trim exposed stracktraces.


view this post on Zulip iago-lito (Jul 31 2026 at 07:50):

In the following situation:

#-------------------------------------
module Internal
struct WrongInput <: Exception end
f(_) = throw(WrongInput())
f(x::Int) = x
g(x) = f(x)
h(x) = g(x)
export h
end

#-------------------------------------
module Exposed

using ..Internal
struct NiceReport <: Exception end

p(x) = h(x)
q(x) = p(x)
welcome(x) =
    try
        q(x)
    catch e
        e isa Internal.WrongInput && rethrow(NiceReport())
        rethrow(e)
    end
export welcome

Base.showerror(io::IO, ::NiceReport) =
    print(io, "Don't worry it's not that bad just try again <3")

end

#-------------------------------------
# User.
using .Exposed

my_u(x) = welcome(x)
my_v(x) = my_u(x)
my_w(x) = my_v(x)

my_w("hi")

User obtains the following stacktrace:

ERROR: LoadError: Don't worry it's not that bad just try again <3
Stacktrace:
  [1] f(::String)                   <<
    @ Main.Internal ./test.jl.jl:4  <<
  [2] g(x::String)                  <<
    @ Main.Internal ./test.jl.jl:6  <<
  [3] h(x::String)                  <<
    @ Main.Internal ./test.jl.jl:7  <<  Garbage (from user POV).
  [4] p(x::String)                  <<  Useless for debugging: not a bug (in the lib).
    @ Main.Exposed ./test.jl.jl:17  <<
  [5] q(x::String)                  <<
    @ Main.Exposed ./test.jl.jl:18  <<
  [6] welcome(x::String)            <<
    @ Main.Exposed ./test.jl.jl:21  <<
  [7] my_u(x::String)            <<
    @ Main ./test.jl.jl:37       <<
  [8] my_v(x::String)            <<
    @ Main ./test.jl.jl:38       <<   *USEFUL* (from user POV).
  [9] my_w(x::String)            <<
    @ Main ./test.jl.jl:39       <<
 [10] top-level scope            <<
    @ ./test.jl.jl:41            <<
 [11] top-level scope            <<
    @ REPL[1]:1                  <<
in expression starting at ./test.jl:41

Is there a way I could reliably trim frames [1] to [6] so as not to bury them into the lib implementation detail? The relevant info for them only starts on frame [7], and the deepest the lib the further up they need to scroll so as to just read the report.

view this post on Zulip iago-lito (Jul 31 2026 at 07:52):

Using throw(NiceReport()) instead of rethrow(NiceReport()) does cut it exactly where it should, but then the caused by section still flushes the report up.

view this post on Zulip iago-lito (Jul 31 2026 at 07:54):

(currently investigating Base.current_exceptions)

view this post on Zulip iago-lito (Jul 31 2026 at 07:59):

(nah, it doesn't seem we can mutate the returned ExceptionStack, can we?)

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 07:59):

I don't think so, but also, I think it's a bad idea to even try. A stack trace should contain the entire call stack so it's maximally usable for debuggning. Once you try messing with it to improve user friendliness, you should not be throwing un-caught exceptions, but instead print an error to stderr or something like that

view this post on Zulip iago-lito (Jul 31 2026 at 08:01):

Just printint to stderr would drop frames [7] to [11] which are useful to user IMO.

view this post on Zulip iago-lito (Jul 31 2026 at 08:02):

Also the purpose of rethrow(e) in case of unexpected exception is to really obtain the full stacktrace in case the lib itself needs debugging, so I wouldn't worry about this?

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 08:02):

It's going to be difficult to determine beforehand what's useful to a user. What's to say the error isn't truly caused by something internal in your library?

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 08:02):

When I say difficult, I mean that it's not possible at all. You can't have that information

view this post on Zulip iago-lito (Jul 31 2026 at 08:03):

This pattern does (IMO):

try
   f()
catch e
   e isa ExpectedExceptionType || rethrow(e) # Obtain full stracktrace for debugging.
   # or handle e gracefully, forgetting about internal detail.
end

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 08:04):

Ahh so you'd want something like Python's raise X from None. Yeah that makes sense

view this post on Zulip iago-lito (Jul 31 2026 at 08:04):

Don't know that one, but yeah why not :)

view this post on Zulip iago-lito (Jul 31 2026 at 08:05):

I wish to trim whichever part of the stack collected before the point I can assert that detail doesn't matter.

view this post on Zulip iago-lito (Jul 31 2026 at 08:05):

(and I take responsibility for this as the lib author ofc)

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 08:09):

Ah yeah I see. I don't think that exists. But it would be nice to have

view this post on Zulip iago-lito (Jul 31 2026 at 08:10):

Siigh, should I request it you think or is there already relevant momentum in that direction?

view this post on Zulip Jakob Nybo Andersen (Jul 31 2026 at 08:20):

I think Julia's errors have gotten very little attention in general

view this post on Zulip iago-lito (Jul 31 2026 at 08:22):

Alright. I'll report as a feat request unless someone else teaches us something about this today :)

view this post on Zulip iago-lito (Jul 31 2026 at 08:25):

(re)throw(e; afresh=true) or sth would be nice.

view this post on Zulip Mason Protter (Jul 31 2026 at 15:09):

I'd really like a way to trim out noise from stacktraces too. The context I have this from is writing code like this:

macro outline(args...)
    fargs = esc.(args[1:end-1])
    body = args[end]
    @gensym f
    quote
        $f($(fargs...),) = $body
        @noinline $f($(fargs...),)
    end
end

function my_sqrt(x)
     if x < 0
          @outline x throw(ArgumentError("my_sqrt does not take negative arguments, got $x"))
    end
    [...]
end

Here @outline automates the process of making a function barrier for me so that the error-path doesn't clog up the generated code for my_sqrt, which helps optimization, and then the value can be used in the error message without worrying about all the involved io/string operations.

But the downside is that it clogs up stacktraces with irrelevant information.

view this post on Zulip iago-lito (Jul 31 2026 at 18:30):

https://github.com/JuliaLang/julia/issues/62594.. let's see ¯\_(ツ)_/¯

view this post on Zulip iago-lito (Jul 31 2026 at 20:53):

Relevant: https://github.com/JuliaLang/julia/issues/40138


Last updated: Aug 10 2026 at 05:52 UTC