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