Current code state

In the last installment I became disappointed with myself for not doing adequate error handling. It's early in the project so I became tempted to skip that and put it off for later. I decided while writing my blog entry that I'd made a bad decision and one that could be used as a teaching moment. I've since gone back and fixed that issue.

Process error codes

A lot of times the exit code that your process returns means more than just pass/fail. A great many projects stipulate codes for different conditions and if your program is meant to be used by an automated system it can be important. So I spent a lot of time learning how to do this in Rust.

This can be done if you make your own object that responds to the Termination trait. I was able to follow much of this blog to get there. Unfortunately though I couldn't get as far as implementing ? from that blog entry because the interface to do so has changed from v1 to v2 and v2 is completely different from v1. The Try trait now works with "Residuals" and I struggled to understand the setup. I tried just getting it done by using "try_v2" to get the derive macro but this didn't work immediately.

This can only be done by using "unstable" rust API. I decided I didn't want to do that and instead of learning this now anyway I'll pick it up later. I could either just not use ? in main or I could give up on the idea of error codes for each type of error. I decided on the latter so I just let rust report the error when it exits and return ExitCode::FAILURE per the default behavior when you return a Result.

Error type scheme

There are a bunch of different ways in which people handle errors in Rust. The most common ones obviously use Result or Optional, but there's also a lot of differences in the Error type used and how rigorous the API is about what errors come out of what functions.

I decided that I don't really care that much for the conditions I have need ot dealing with so far. I already selected axum for the server library and it makes sure all errors are handled, so I just need to handle the conditions that prevent me from handing off to this library by calling serve. So I went with the anyhow library. This just wraps up the errors in something dynamic I can hand up the change to the part that reports the condition to the user and exits. Rust has another method of dealing with these, panic, but I'm maybe too into the idea I should propagate up more expilicitly.

This anyhow library lets me attach further context to the error so when it gets reported it provides additional information on top of whatever got discovered by the error generation part. Here's an example:

let listener = tokio::net::TcpListener::bind(&self.config.listen_address)
    .await
    .with_context(|| format!("Could not bind network interface at `{}`", &self.config.listen_address))?;

This is a little sloppier than I'm usually comfortable with--just mashing all error types into one bucket-- but in this case I think I really need to silence that part of my brain and be good with this. There's no need to do more than this at this time and I don't think there ever will be.

So after a couple days of messing about coming up with ways to propagate the info up with explicit errors at each level, and not being happy with any of it, I decided that this anyhow library is what I want. The same author makes another helpful library for custom errors but I don't think I'm going to go that way.