The text doesn't do it justice, because it has colors, so here's a screenshot too:

That said, we're going to be converting ersatz to a library eventually, so that it
can be used by sup to speak ICMP.

You may argue that this is still a fine use of panics: if an application relies on
a library like ersatz to do networking, it's unlikely the application will be able
to do anything useful it we can't get ahold of the default network interface.

Then again, it's not impossible! The application might be a server, and it might use
ersatz in a handler - in that case, we wouldn't want it to bring down the whole server.
It might also fall back to another method of doing networking (again, unlikely, but
not impossible). In that scenario, the application would have to catch the panic with
something like std::panic::catch_unwind()

$ cargo run --quiet
thread 'main' panicked at 'This will be caught', src\main.rs:15:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Call went wrong: Any
Listening for packets...

Mhh, this isn't ideal. First off, the “panic” message is still shown - if we
still had better-panic installed, or if we exported RUST_BACKTRACE=1,
we'd see the whole backtrace - even though we catch it!

Second, from where we catch it (the Err() arm of the match res), we don't
see the error message. The E in Result<T, E> for catch_unwind is
Box<dyn Any + Send>.

If we want to see the message, we have to downcast the Any - which can also
fail. Here, the Any contains a &str, so we can get it out:

$ cargo run --quiet
thread 'main' panicked at 'This will be caught', src\main.rs:15:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Call went wrong, error is "This will be caught"
Listening for packets...

…but that won't be true every time. If we change our panicking code to this:

$ cargo run --quiet
thread 'main' panicked at 's should not be none', src\libcore\option.rs:1190:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Any', src\libcore\result.rs:1165:5

Note that this function may not catch all panics in Rust. A panic in
Rust is not always implemented via unwinding, but can be implemented by
aborting the process as well. This function only catches unwinding panics,
not those that abort the process.

i.e, the library and proc names now need to be 'static. Which is okay if you're
loading libraries and procs from string constants (already 'static), but not if
you're implementing a REPL, where you'll be passing references to strings that were
just parsed from user input, and which are short-lived.

Cool bear's hot tip

Why did we change name to have the 'static lifetime in the first place?

Imagine we have a top-level error handler that prints fatal errors, complete
with their backtrace. It'll need to own an instance of whatever our Error type
is, and that instance needs to own sufficient data to format the error.

This is because the error is actually formatted way after the problematic
function has returned and all its locals have been dropped.

Why is that happening? Well, with failure, the return type is failure::Fallible<T>,
which is equivalent to std::result::Result<T, failure::Error>. And failure::Error
contains a dyn failure::Fail, which has a blanket implementation for all types
that implement std::error::Error.

And std::ffi::NulError definitely implements std::error::Error. So if we want
our failure::Error to contain our error instead, we'll have to be a tiny bit
more specific:

But this raises the question: do we really need to use failure in loadlibrary?

Probably not. It would be less awkward for users to match directly on loadlibrary::Error
rather than have to go through downcast_ref. We can still use failure in application
code!

Also, is it right for Library::new() and Library::get_proc() to return the same error
type? They have some common errors (invalid name), but it's impossible for new() to return
ProcNotFound, and for get_proc() to return LibraryNotFound, which makes it awkward
to match for those error conditions.

If we were still writing error types manually, I would be extremely too lazy to
do it. But with thiserror, we'll be there in no time.

These seem fine. bind! is meant to load libraries and procs with static
names, and we have no way to bubble up the error since they're lazily loaded
whenever they're first called - their unsafe extern "C" prototype may
not even allows us to return an error (and if they do, it's an u32).

But, we still benefit from the error handling work we did on loadlibrary
earlier. Previously, if we specified a wrong library, we'd just get a message like
this:

We could go down the rabbit hole further if we intended to match and handle
those errors - I'd probably let netinfo have its own error type, and have
ersatz's Error type wrap it.

But that's absolutely unnecessary - as was most of this article for an
exploratory codebase. It was a nice showcase of the different crates you can
use to improve error handling in your codebase though, so I hope you liked it!