In complex code where you can’t completely eliminate the possibility of a panic, backtraces can be made significantly more informative by including the values of important local variables. I don’t actually have a good example to hand in Rust, but back in 2006 when I was working on monotone (Graydon’s big project before Rust, if you haven’t heard of it; pretty much the Velvet Underground of DVCSes) we made very heavy use of this. A decent self-contained example from that code (C++) is the normalize_path function, which begins
MM(inT) means “If anything in this function causes it to throw an exception, the error message that eventually gets printed should include the contents of the variable inT.” It then goes on to do a bunch of string mangling that could conceivably have a bug that causes it to run off the end, or something like that; if it does, you get to know what input caused the problem. Later in the code you will see uses of an I(expression) macro, which is this codebase’s version of assert!, and pairs with MM.
I think this would be a valuable and easy feature to add to Rust, and the main reason I’m posting about it here instead of just going and hacking it up and submitting a PR is that I don’t have a good name for it. MM is much too cryptic, obviously. (It originally comes from GNU Nana, which had this entire vocabulary of one- and two-character assertion-related macros.) The implementation of MM calls things that are currently tagged to be printed “musings”, which is at least a word, but not a terribly clear one. Anyone have a suggestion?
This feels like you’re trying to float a variable to the top level from somewhere deeper into the program, so surface(inT) could work. Another alternative is to think of it as shining a light on something, so highlight(inT); introspect(inT) also comes to mind. It’s always difficult to coin new terminology that doesn’t clash with at least one pre-existing domain though, so I fully expect to have stepped on someone’s toes with one of those.
I think there’s a way to see if you’re currently panicking. You could couple that with some dummy type who’s drop implementation is simply a check to see if you are panicking and then try to print that string.
It’s probably not the nicest way of doing things, nor will it catch all panics, but it may be good enough. Don’t forget that panicking while you’re already panicking will usually abort!
For naming, I’d rather not try to coin any new verbiage for this and instead do something simplistic like show_in_panics!(foo) that is at least potentially self-evident.
Your demo made me realize that this idea has a nasty problem with reference lifetimes/borrow conflicts.
Using lxrec’s name suggestion for now, and imagining your code being used for the underlying implementation, we would have
fn munge(orig_s: &str) -> String {
show_on_panic!(orig_s);
let mut s = orig_s.to_owned();
show_on_panic!(s);
// ... stuff which modifies s in place ...
s
}
with show_on_panic!(s) expanding to something like
let _gensym_0002 = PanicPrint::new(&s);
and now we have an object live for the entire function, holding an immutable reference to s, which is going to block whatever the rest of the function does to modify s in place.
We could use a raw pointer instead, but that would give up lifetime safety as well as mutability safety, and I’m worried that with sufficiently complicated control flow and/or lifetimes we would wind up maybe trying to print an object that’s already been destroyed. Two-phase borrow and NLL both address related problems but I do not know if they address this case.
Maybe this is addressed sufficiently by what you already did to make the ShowOnPanic struct itself be tied to the lifetime of the object? You had
Another alternative is you could store a string representation by either using format!() or the to_string() method available to anything implementing Display. Of course that now means you don't have a zero-cost mechanism for doing something only during a panic...