So, regarding #[effect(no_panic)] or #[deny(panic)], while it may seem simple from a surface syntax perspective, what this implies is an effect system which amounts to no_panic fn (much like const fn), because you have to ensure that the function being type checked can’t call other things that may panic. When you introduce traits this becomes quite hairy. If you don’t have some way to use traits, then the mechanism becomes mostly useless as a immense part of rust becomes unusable.
Let’s try to sketch what such a system might be and think of possible problems…
Thinking in terms of effects, no_panic is a restriction of the base effect impure + partial where we have that panic <: partial, i.e: panic is a sub-effect of partial diverging by a single mechanism (as opposed to other forms of non-termination…).
Now like with const, it is desirable to be polymorphic in the question “can this panic?” such that given:
?no_panic fn twice_ptr<T>(x: T, fun: ?no_panic fn(T) -> T) -> T {
fun(fun(x))
}
?no_panic fn twice_gen<T, F: ?no_panic Fn(T) -> T>(x: T, fun: F) -> T {
fun(fun(x))
}
… the bodies of the HoFs above can’t panic by themselves, but if you call twice_ptr with fun : fn(T) -> T, then twice_ptr(x, fun) has the panic effect and if fun : no_panic fn(T) -> T then twice_ptr(x, fun) is also no_panic.
However, this gets complicated once you throw in the generic methods of traits themselves.
Take for example: Iterator defined as (relevant parts only…):
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
fn for_each<F>(self, f: F) where F: FnMut(Self::Item);
}
We would like to reuse use this trait and define:
struct Foo;
impl ?no_panic Iterator for Foo {
type Item = Foo;
fn next(&mut self) -> Option<Self::Item> { Some(Foo) }
fn for_each<F>(self, f: F) where F: FnMut(Self::Item) {
...
}
}
This desugars to:
struct Foo;
impl Iterator for Foo {
type Item = Foo;
?no_panic fn next(&mut self) -> Option<Self::Item> { Some(Foo) }
?no_panic fn for_each<F>(self, f: F) where F: ?no_panic FnMut(Self::Item) {
...
}
}
This works fine so far. Let’s try something more bold (directly desugared):
struct Bar;
impl Iterator for Foo {
type Item = Foo;
no_panic fn next(&mut self) -> Option<Self::Item> { Some(Foo) }
no_panic fn for_each<F>(self, f: F) where F: no_panic FnMut(Self::Item) {
...
}
}
We have a problem now. Given this definition, if we write:
fn baz<It: Iterator, F: FnMut(It::Item)>(iter: It, fun: F) {
iter.for_each(fun);
}
Then we have one of two problems:
- You can’t call
baz with an It: no_panic Iterator because if that were allowed, then you could have executed a panic inside for_each since fun can panic according to the signature of baz. If you did permit it, then the type system would be unsound.
- Because calling
baz with It: no_panic Iterator can’t be permitted to preserve soundness, we give up the property that T: no_panic Trait ⊢ T: Trait. Giving that property up means less code reuse.
How to deal with this I’m not sure.
EDIT: I hope this was somewhat comprehensible… apologies if it was gibberish 