Per-thread panic hooks
Problem statement
Arbitrary panic hooks are borderline incompatible with unsafe code due to them being safe to set & call, thus in theory forcing unsafe implementors to either never trigger a panic, even if caught (which involves linker hacks to prove in the general case), or to ignore this.
Setting & unsetting a panic hook by hand before & after an unsafe block is not a fix for this either, since the panic hook is set globally and another thread may safely unset it.
Motivating examples or use cases
Since global panic hooks are safe to set and unset, they may be able to access global state that libraries expose in a reentrant manner. This on its own is harmless, but libraries may soundly break their own safety invariants so long as they ensure such invariants are restored before calls into them return. Taken together, it is possible for a call into a library to panic internally before relevant global state is corrected, allowing a call into the same library from within the hook to observe these broken invariants. Setting a scoped panic hook would prevent this, as it would enable the hook setter to control what is run at the moment of panic and - crucially - ensure an unstrusted global hook is not run.
As setting such a hook is safe, setters cannot rely on hook behaviour when calling arbitrary untrusted code, but may do so to ensure that otherwise-trusted code does not have unintended behaviour on panic (e.g. an untrusted panic hook being invoked because of an erroring arithmetic operation).
Solution sketch
pub fn with_hook<'scope, F, T>(hook_fn: F, f: impl FnOnce() -> T + 'scope) -> T
where
F: Fn(Option<&(dyn Fn(&PanicHookInfo<'_>) + 'scope)>, &PanicHookInfo<'_>)
+ 'scope,
T: 'scope,
This function would then update a thread-local static hook which, if missing, forwards to the global hook.
Alternatives
N.B.: This was previously (2026-08-13) listed as the solution sketch.
Mirror panic::{set, take, update}_hook, without Send + Sync bounds:
// in std::panic
fn set_thread_hook(hook: Box<dyn Fn(&PanicHookInfo<'_>) + 'static>)
fn take_thread_hook() -> Box<dyn Fn(&PanicHookInfo<'_>) + 'static>
fn update_thread_hook<F>(hook_fn: F)
where
F: Fn(&(dyn Fn(&PanicHookInfo<'_>) + 'static), &PanicHookInfo<'_>) + 'static
The main API alternative that comes to mind would be of the form
fn with_hook<T>(hook: Box<dyn Fn(&PanicHookInfo<'_>) + 'static>, f: impl FnOnce() -> T) -> T
where the closure f is invoked with hook as the panic hook. I could imagine this coexisting with the above proposal (it is a strictly weaker form of it and would be implementable in its terms), and has the advantage that the panic hook cannot thus altered for a function's caller as well; I'm unsure if this would be a desirable feature due to the better encapsulation of behaviour or a negative since it makes setting up relevant thread-global state harder. Some expressivity could be gained by relaxing the 'static bound on the hook here, which would require unsafe code to express with the current proposal (but would still be doable). I would also be happy saying we as std offer both.
Another option would be the status quo, since it's not the case that the panic hook is widely used in unsound ways.
Unresolved questions
What should the behaviour of newly-spawned threads be when a thread hook is set? Should it be copied from the spawning thread or default to none?
Should these be unsafe fns so unsafe code may rely on the hook being "correct" within a scope?
- Per discussion: No, reasoning about safety invariants in panic hooks is probably not the right approach and comes with a large amount of cognitive complexity
Links and related work
The conversation on the lang zulip re: handle_alloc_error possibly panicking and my past suggestion to make handle_alloc_error always abort.
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
Per-thread panic hooks
Problem statement
Arbitrary panic hooks are borderline incompatible with unsafe code due to them being safe to set & call, thus in theory forcing unsafe implementors to either never trigger a panic, even if caught (which involves linker hacks to prove in the general case), or to ignore this.
Setting & unsetting a panic hook by hand before & after an unsafe block is not a fix for this either, since the panic hook is set globally and another thread may safely unset it.
Motivating examples or use cases
Since global panic hooks are safe to set and unset, they may be able to access global state that libraries expose in a reentrant manner. This on its own is harmless, but libraries may soundly break their own safety invariants so long as they ensure such invariants are restored before calls into them return. Taken together, it is possible for a call into a library to panic internally before relevant global state is corrected, allowing a call into the same library from within the hook to observe these broken invariants. Setting a scoped panic hook would prevent this, as it would enable the hook setter to control what is run at the moment of panic and - crucially - ensure an unstrusted global hook is not run.
As setting such a hook is safe, setters cannot rely on hook behaviour when calling arbitrary untrusted code, but may do so to ensure that otherwise-trusted code does not have unintended behaviour on panic (e.g. an untrusted panic hook being invoked because of an erroring arithmetic operation).
Solution sketch
This function would then update a thread-local static hook which, if missing, forwards to the global hook.
Alternatives
N.B.: This was previously (2026-08-13) listed as the solution sketch.
Mirror
panic::{set, take, update}_hook, withoutSend + Syncbounds:The main API alternative that comes to mind would be of the formwhere the closurefis invoked withhookas the panic hook. I could imagine this coexisting with the above proposal (it is a strictly weaker form of it and would be implementable in its terms), and has the advantage that the panic hook cannot thus altered for a function's caller as well; I'm unsure if this would be a desirable feature due to the better encapsulation of behaviour or a negative since it makes setting up relevant thread-global state harder. Some expressivity could be gained by relaxing the'staticbound on the hook here, which would require unsafe code to express with the current proposal (but would still be doable). I would also be happy saying we as std offer both.Another option would be the status quo, since it's not the case that the panic hook is widely used in unsound ways.
Unresolved questions
What should the behaviour of newly-spawned threads be when a thread hook is set? Should it be copied from the spawning thread or default to none?
Should these be
unsafe fns so unsafe code may rely on the hook being "correct" within a scope?Links and related work
The conversation on the lang zulip re:
handle_alloc_errorpossibly panicking and my past suggestion to makehandle_alloc_erroralways abort.What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution: