dtolnay/no-panic

Add some documentation for how it works?

Closed this issue · 1 comments

i.e. how does it know whether functions called by a no_panic function panic?

Hi. I wanna try to explain, but since I am not an author of the crate, I might be mistaken somehow (also sorry for my English). Hope that'll help a bit.

In README mentioned crate dont_panic, and current crate is based on it. Lets look there. That crate provides:

  1. dont_panic! macro;
  2. call(f: F) function (which is based on that macro);
  3. dp_assert! macro (not interesting).

dont_panic!

First we gonna figure out how dont_panic! works. If we look on it's code, we'll see that it simply calls function. But (and that is explained in comment) this function is extern, and doesn't exist in any other object file (this is "decided" by function name, you still can export such function and I assume macro won't work). So, idea here: if compiler optimizes out dont_panic! invocation, there is no call to rust_panic_called_where_shouldnt, linker won't attempt to find that function, and program compiles. Otherwise, call to rust_panic_called_where_shouldnt stays, and you get a linking error.

NOTE: macro name might be slightly confusing - it does not detect panic, it just detect whether it is optimized out or not; see example.

NOTE 2: because debug builds does not perform optimizations, this macro doesn't work correctly in debug builds - code is never optimized out!

call(f: F)

Okay, now lets figure out call(f: F). It's source:

pub fn call<T, F: FnOnce() -> T>(f: F) -> T {
    struct DontPanic;
    impl Drop for DontPanic {
        fn drop(&mut self) {
            dont_panic!();
        }
    }

    let guard = DontPanic;
    let result = f();
    core::mem::forget(guard);
    result
}

What is happening here:

  1. Define DontPanic and impl Drop for DontPanic (with dont_panic! inside);

  2. Create instance of type DontPanic (guard);

  3. Call provided closure f;

  4. If compiler can prove that closure f panic-free:
    3.1 Execution proceeds to forget(guard);
    3.2 forget doesn't invoke Drop, so drop(&mut self) (and dont_panic! inside) is optimized out and program compiles;

  5. If compiler failed to prove that closure panic-free:
    4.1 Unwinding begins;
    4.2 During unwinding guard is getting dropped, and it's drop(&mut self) method is called;
    4.3 Since call to drop(&mut self) is not optimized out, dont_panic! is still here and we get a linking error.

Back to no-panic

Oookay, back to current crate. Now it should be easy. Link to no_panic macro. This macro is just parses input (which is function) and wraps its body with something like call(f: F) (it's pretty similar, right?). Pretty error message is provided with #[link_name = "..."] "hack".

Thats it!