Open gnzlbg opened 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.
Sometimes it is necessary to make sure that certain expressions (like function calls) do not panic, for a couple of reasons:
panic=unwind
, a function that "never returns" (-> !
) can still return via their unwinding path. Some unsafe code might only be correct if an operation does not panic.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 thenoexcept(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)
). Thenoexcept(expr)
operation returns a boolean that istrue
if theexpr
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 forstd::vector<T>
that is noexcept, if thestd::vector<T>::swap
method is alsonoexcept
. One can do it like this:Note the two chained uses of the
noexcept
keyword, the outer one being the function qualifier taking abool
, and the inner one being thenoexcept(expr)
which returns true at compile-time ifa.swap(b)
cannot throw. That is, ourswap<T>
function will benoexcept(true)
if and only ifstd::vector<T>::swap
is alsonoexcept(true)
forT
.