Centril / rfc-effects

Preparing an RFC on effect polymorphism in Rust with focus on 'const'
10 stars 0 forks source link

Panic #13

Open gnzlbg opened 5 years ago

gnzlbg commented 5 years ago

Sometimes it is necessary to make sure that certain expressions (like function calls) do not panic, for a couple of reasons:

Rust users have come up with clever solutions to try to make sure that some functions cannot ever panic, e.g., the no-panic crate, but these techniques are brittle, not composable, etc.

A function that does not panic, either returns its return type, or it aborts. When -C panic=abort, this is the case for all functions, so it is unclear to me whether the effect should be "panicking" or "unwinding", and whether it would make sense for all functions to be no-panic when -C panic=abort (sounds like a sound thing to do though).

Prior art

There is prior art about unwinding as an effect in other programming languages. I'll just add C++ here because it is a language in the same space as Rust that has a quite powerful mechanism for this.

C++

Being generic over non-throwing exceptions is possible in C++ via the noexcept(const-bool) function qualifier and the noexcept(expr) -> const-bool operator. The qualifier is part of a function and function pointer type, such that functions can be overloaded depending on the qualifier, etc. and is used to indicate whether a function can throw exceptions (noexcept(false)) or whether it cannot throw (noexcept(true)). The noexcept(expr) operation returns a boolean that is true if the expr cannot throw and false otherwise. These two features combined with weakly-typed templates and the execution of arbitrary compile-time predicates allow writing very expressive generic code that is generic over whether a function can throw, and the C++ standard library uses this extensively.

For example (dumbed down to the C++ standard spec level, the reality is a bit more subtle), suppose you want to provide a generic swap function for std::vector<T> that is noexcept, if the std::vector<T>::swap method is also noexcept. One can do it like this:

template <typename T>
void swap(std::vector<T>& a, std::vector<T>& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
}

Note the two chained uses of the noexcept keyword, the outer one being the function qualifier taking a bool, and the inner one being the noexcept(expr) which returns true at compile-time if a.swap(b) cannot throw. That is, our swap<T> function will be noexcept(true) if and only if std::vector<T>::swap is also noexcept(true) for T.

gnzlbg commented 5 years ago

One example where this might be very useful is ptr::drop_in_place. When called on an aggregate it drops all fields sequentially. If one field drop glue panics, it needs to catch the panic, and continue dropping fields, and once all other fields are successfully dropped, then re-raise the panic (if a second field panics, then we get a double panic).

For homogeneous aggregates (all fields have the same type, like in an array or a slice), if T::drop cannot panic then you don't have to do any of this.