# Panic bounds in the type system level for guaranteeing panic-free code

**URL:** <https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911>\
**Category:** language design\
**Created:** [November 23, 2023, 9:36pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911 "2023-11-23T21:36:54Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![alonely0](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/alonely0/32/9632_2.png) [@alonely0](https://internals.rust-lang.org/u/alonely0)\
**Post date:** [November 23, 2023, 9:36pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/1 "2023-11-23T21:36:54Z")

</div>

# Why

In today's Rust, the concept of panic, a critical runtime error, exists outside of the type system. While this allows us to code without thinking much about it, people who code in embedded, bare-metal, or do FFI, really do have to. There have been multiple efforts to tackle this outside of the language, like extra tooling or linker scripts, but these are workarounds and cumbersome at best.

# How

We currently have different traits for functions: `Fn`, `FnOnce`, and `FnMut`. On closures, these are implemented based on what the closure does with its captured variables. My idea is to have another `Fn` trait for those that do not panic, e.g. `FnNoPanic`, which is implemented by all functions that may never (safely) trigger stack unwinding. Then, we would be able to do `let x: impl FnNoPanic = || foo();` in order to ensure panic safety. This way, if `foo()` called code that, in turn, could panic, it would result in a compilation error.

## Attribute

In order to have these checks performed at the function declaration boundary, the most straightforward way would be an attribute. We could have a `#[no_panic]` attribute that ensures that the function implements the `FnNoPanic` trait. With this header, the function will not compile if it may panic.

## Semver

However, this is a HUGE semver hazard. Changing the internal code of a function (not the interface) would have an effect on the callers! For this reason, I propose this to not be part of semver unless the attribute is used. I fear this is not enough and would create lots of footguns, so a reasonable solution would be to have the `FnNoPanic` trait be only implemented if the attribute is present or all called functions have this attribute; and then have a way to unsafely implement it if a function from another crate doesn't implement it, but should.

## Function pointers

Functions pointers do and will not make any guarantees about panic safety, so any function pointer that can't be tracked while type-checking (function pointers as function arguments, for example), are assumed not to implement `FnNoPanic`.

## External calls

Any external function (rust ABI or not) must implement the `FnNoPanic` trait unsafely. This is because the compiler is unable to check them directly.

## Unsafe implementation of the attribute

Unsafe attributes are a feature that has been discussed for quite long, and we could use them to implement this unsafely. Bikeshed: `#[unsafe(no_panic)]`.

---

<div class="post-metadata">

**Author:** ![simonbuchan](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/simonbuchan/32/9390_2.png) [@simonbuchan](https://internals.rust-lang.org/u/simonbuchan)\
**Post date:** [November 23, 2023, 11:01pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/2 "2023-11-23T23:01:09Z")

</div>

Related: the [`c_unwind` feature](https://github.com/rust-lang/rust/issues/74990) is the opposite feature for extern ABIs: this sounds like an `extern "rust-no-unwind"` in those terms?

Not sure if Rust could do variance across ABI types, but that would make the Fn trait variant a bit easier, though I think you would want variants of all three.

There might be way to make the Fn ABI an associated type?

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [November 24, 2023, 1:32am UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/3 "2023-11-24T01:32:54Z")

</div>

Probably the best way to handle this is via effects.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [November 24, 2023, 12:37pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/4 "2023-11-24T12:37:10Z")

</div>

> [@alonely0](#):
>
> We currently have different traits for functions: `Fn`, `FnOnce`, and `FnMut`.

> [@alonely0](#):
>
> My idea is to have another `Fn` trait for those that do not panic, e.g. `FnNoPanic`, which is implemented by all functions that may never (safely) trigger stack unwinding.

Wouldn't we need `FnMutNoPanic` and `FnOnceNoPanic` too then, in addition to `FnNoPanic`?

---

<div class="post-metadata">

**Author:** ![jjpe](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jjpe/32/9779_2.png) [@jjpe](https://internals.rust-lang.org/u/jjpe)\
**Post date:** [November 24, 2023, 1:25pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/5 "2023-11-24T13:25:30Z")

</div>

> [@alonely0](#):
>
> My idea is to have another `Fn` trait for those that do not panic, e.g. `FnNoPanic`, which is implemented by all functions that may never (safely) trigger stack unwinding.

Without commenting on the rest of the post, I think that you'd need 3 new traits, one analog for each of the current traits. Or, alternatively, a marker trait that can be `+ NoPanic` slapped on each of the current Fn traits, which would likely require additional work eg in allowing `impl Trait` in let bindings and type fields.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [November 24, 2023, 1:29pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/6 "2023-11-24T13:29:49Z")

</div>

Dereference a pointer inserts an (aborting) panics on dereferencing unaligned pointers when using debug assertions. Would this also be forbidden for `FnNoPanic`? In the future we will have likely have even more cases of UB for which we check. Having those extra checks be a breaking change would be bad.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [November 24, 2023, 1:42pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/7 "2023-11-24T13:42:15Z")

</div>

> [@jjpe](#):
>
> Or, alternatively, a marker trait that can be `+ NoPanic` slapped on each of the current Fn traits, which would likely require additional work eg in allowing `impl Trait` in let bindings and type fields.

Is `Iterator::map` `NoPanic`? I've posted to the "effects system" threads before that my concerns are around the implications for higher-order functions and communicating the traits/effects across those boundaries.

> [@alonely0](#):
>
> There have been multiple efforts to tackle this outside of the language, like extra tooling or linker scripts, but these are workarounds and cumbersome at best.

We have `panic=abort` and `panic=unwind`. How about `panic=disallow` (could leave the symbol undefined or have compiler magic that directly detects usage of `panic!`) or `panic=halt` for these platform targets?

---

<div class="post-metadata">

**Author:** ![cg909](https://avatars.discourse-cdn.com/v4/letter/c/90ced4/32.png) [@cg909](https://internals.rust-lang.org/u/cg909)\
**Post date:** [November 24, 2023, 1:43pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/8 "2023-11-24T13:43:56Z")

</div>

> [@jjpe](#):
>
> I think that you'd need 3 new traits, one analog for each of the current traits. Or, alternatively, a marker trait that can be `+ NoPanic` slapped on each of the current Fn traits,

A marker trait would probably be enough. There just needs to be compiler magic, so that `<Foo as Fn(…)>::call()` `where Foo: FnNoPanic` (and the same for `FnMut` and `FnOnce`) is also inferred as `#[no_panic]` as a refinement.

It would be nice, if that marker trait could also be used with `async` blocks, e.g.

```rust
fn foo() -> impl Future<Output=()> + NoPanic {
  #[no_panic]
  async {
    todo!(); // -> compiler error
  }
}

```

So IMHO calling the trait `NoPanic` or `PanicFree` would be better to allow extending it to async code.

---

<div class="post-metadata">

**Author:** ![jjpe](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jjpe/32/9779_2.png) [@jjpe](https://internals.rust-lang.org/u/jjpe)\
**Post date:** [November 24, 2023, 1:51pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/9 "2023-11-24T13:51:17Z")

</div>

> [@mathstuf](#):
>
> Is `Iterator::map` `NoPanic`?

Is there a specific reasons it shouldn't be inferred to be `NoPanic`?

I mean we could talk about how it combines with its closure argument, but does `Iterator::map` itself do anything that could panic? The [source](https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#801-804) suggests that it does not.

> [@mathstuf](#):
>
> We have `panic=abort` and `panic=unwind`. How about `panic=disallow` (could leave the symbol undefined or have compiler magic that directly detects usage of `panic!`) or `panic=halt` for these platform targets?

I guess my question is, what happens to a `panic!()` call (and calls with a similar effect eg calling `Result::unwrap`) if panic=halt/disallow? Is it silently swallowed? Or is rustc expected to yield a compile error?

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [November 24, 2023, 2:26pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/10 "2023-11-24T14:26:42Z")

</div>

> [@jjpe](#):
>
> but does `Iterator::map` itself do anything that could panic?

`.next()` can potentially panic for un-`Fuse`d `Iterator`s.

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [November 24, 2023, 3:44pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/11 "2023-11-24T15:44:52Z")

</div>

This is not actually relevant to effects, but `FusedIterator` doesn't promise anything about panics. It only promises that `next()` will not return `Some` if it has previously returned `None`, and panicking is not returning.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [November 24, 2023, 5:12pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/12 "2023-11-24T17:12:33Z")

</div>

Yes, true. However, my understanding is that `.fuse()` is typically used to handle iterators that panic if `.next()` is called after they return `None` once (IIRC, I/O iterators like file readers, pipe readers, or network streams tend to do this).

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [November 24, 2023, 5:39pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/13 "2023-11-24T17:39:21Z")

</div>

> [@kpreid](#):
>
> It only promises that `next()` will not return `Some` if it has previously returned `None`, and panicking is not returning.

The actual doc-comment is

> Calling next on a fused iterator that has returned `None` once is guaranteed to return `None` again.

I would say that that precludes panicking, it doesn't say that _if_ it returns it returns `None` again.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [November 24, 2023, 6:11pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/14 "2023-11-24T18:11:11Z")

</div>

Yes, `FusedIterator` doesn't protect against an iterator panicking before it reaches the end; I don't think any adaptor could do that. What it protects against is "the iterator already ended; why are you asking again" panics. Anyways, `Iterator::map` can't unconditionally be `NoPanic` based just on the passed-in `F`.

---

<div class="post-metadata">

**Author:** ![quaternic](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quaternic/32/10440_2.png) [@quaternic](https://internals.rust-lang.org/u/quaternic)\
**Post date:** [November 24, 2023, 9:02pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/15 "2023-11-24T21:02:12Z")

</div>

> [@jjpe](#):
>
> > [@mathstuf](#):
> >
> > Is `Iterator::map` `NoPanic`?
> 
> Is there a specific reasons it shouldn't be inferred to be `NoPanic`?
> 
> I mean we could talk about how it combines with its closure argument, but does `Iterator::map` itself do anything that could panic? The [source](https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#801-804) suggests that it does not.

> [@mathstuf](#):
>
> Anyways, `Iterator::map` can't unconditionally be `NoPanic` based just on the passed-in `F`.

I believe the question was intended for clarification of the precise terms. `Iterator::map` is just the function which constructs a new iterator that maps each element, and could indeed be unconditionally `NoPanic`, because it just fills in the iterator `I` and closure `F` as the fields of `std::iter::Map<I,F>`, the iterator it returns.

But yes, `Map::<I,F>::next` could be `NoPanic` only if both of `I::next` and `F::call_mut` are.

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [November 24, 2023, 9:17pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/16 "2023-11-24T21:17:00Z")

</div>

How do you plan to handle panic-free code dependent on panic elimination? For example, would be the following function considered panic-free?

```rust
fn sum(a: [u32; 2]) -> u64 {
   u64::from(a[0]) + u64::from(a[1])
}

```

Technically, indexing may panic (though, in this case it gets eliminated even with `opt-level=0`) and in debug mode without optimizations the addition [generates](https://rust.godbolt.org/z/rezzsMcn5) an explicit panic branch.

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [November 24, 2023, 10:11pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/17 "2023-11-24T22:11:59Z")

</div>

In my opinion, we shouldn't even try to handle panic elimination like that. To make that function panic-less, you would have to rewrite it to something like this instead:

```rust
fn sum([a, b]: [u32; 2]) -> u64 {
   u64::from(a).wrapping_add(u64::from(b))
}

```

Side note: it would be kinda nice to have a `widening_add` kinda like `overflowing_add` but returning the next largest integer type.

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/skifire13/32/7579_2.png) [@SkiFire13](https://internals.rust-lang.org/u/SkiFire13)\
**Post date:** [November 25, 2023, 9:06am UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/18 "2023-11-25T09:06:10Z")

</div>

> [@pitaj](#):
>
> To make that function panic-less, you would have to rewrite it to something like this instead:
> 
> ```rust
> fn sum([a, b]: [u32; 2]) -> u64 {
> u64::from(a).wrapping_add(u64::from(b))
> }
> 
> ```

I think this shows how much work you need to make something panic-less, even for a very simple function like that.. You even need to hide an error state in your program, not for optimizations, but to intentionally not throw an error in debug mode.

I wonder how much panic-less code you can even write without:

- hiding errors in your program state (i.e. return dummy/wrong values);
- hiding representing every panic (even those that can't happen) inside `None`/`Err` that will never happen;
- using `unsafe` to transform panics into UB.

I feel like what is really needed here is some way to statically prove those panics can't happen, but for this you pretty much need dependent types.

> [@pitaj](#):
>
> Side note: it would be kinda nice to have a `widening_add` kinda like `overflowing_add` but returning the next largest integer type.

You can just cast to the next bigger integer type and use `wrapping_add` for that. The problem though is that it becomes harder to compose functions like this since the next `widening_add` will use an even bigger integer type and so on.

---

<div class="post-metadata">

**Author:** ![alonely0](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/alonely0/32/9632_2.png) [@alonely0](https://internals.rust-lang.org/u/alonely0)\
**Post date:** [November 25, 2023, 4:52pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/19 "2023-11-25T16:52:16Z")

</div>

The point of panic-free code is that you have to handle all the cases explicitly, so of course, it's more verbose. Writing panic-free code is even more difficult if you can't prove whether it is actually panic-free, that's why I started this discussion thread. Ergonomics are something that can be tackled later, now we should focus on getting something that can be relied on.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [November 25, 2023, 8:14pm UTC](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911/20 "2023-11-25T20:14:50Z")

</div>

> [@pitaj](#):
>
> Side note: it would be kinda nice to have a `widening_add` kinda like `overflowing_add` but returning the next largest integer type.

What I really want for this is a new `Int<MIN, MAX>` type, so that we can have `Int<A, B> + Int<C, D> → Int<{A+C}, {B+D}>`.

With a type like that, `(x + 2 * y + z)/4` _just works_, rather than needing tricky contortions.

[Next page](https://internals.rust-lang.org/t/panic-bounds-in-the-type-system-level-for-guaranteeing-panic-free-code/19911.md?page=2)
