2 stable releases

1.0.1 Aug 1, 2023

#335 in Command-line interface

0BSD license

6KB

proxit — Unix-style error messages in Rust

proxit is a super simple Rust library that allows you to have consistent, UNIX-style error messages with a little bit less boilerplate by implementing the Termination trait for you.

Why?

All UNIX commandline utilities have displayed their errors in more-or-less the exact same way since the beginning of time:

<utility name>: <error message>

and sometimes:

Usage: <utility name> <utility arguments>
# or
usage: <utility name> <utility arguments>

For example:

$ grep -fakeflags
grep: akeflags: No such file or directory

$ mandoc
bash: mandoc: command not found

$ doas
usage: doas [-Lns] [-C config] [-u user] command [args]

So how did the genius Rust team decide to do it? Let’s check the code:

impl<T: Termination, E: fmt::Debug> Termination for Result<T, E> {
    fn report(self) -> ExitCode {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                io::attempt_print_to_stderr(format_args_nl!("Error: {err:?}"));
                ExitCode::FAILURE
            }
        }
    }
}

Wait… did I see that right?

format_args_nl!("Error: {err:?}"));

Bruh.

How Proxit Works

Using proxit is super simple:

use proxit::MainResult;

fn main() -> MainResult<(), Error> {
	work().into();
}

fn work() -> Result<(), Error> { /**/ }

A MainResult is just a wrapper around a Result<T, E> where T implements Termination, and E implements Display. In practice, T will basically always be () which does implement Termination.

When the main function returns, if the Result within the MainResult is an Ok variant, then the .report() method of T is invoked. Otherwise, E is printed out to standard error (this is why you need to implement Display) prefixed by the string "{argv0}: " where argv0 is the program name.

What if you want to print a usage string though? Well… because simpler and simpler is always better, just call eprintln!() and process::exit(). Printing the usage string yourself also gives you the flexibility to decide between GNU- and FreeBSD-style usage strings ("Usage: {s}") and OpenBSD-style usage strings ("usage: {s}").

use std::process;

use proxit::{self, MainResult, Usage};

fn main() -> MainResult<(), Error> {
	work.into();
}

fn work() -> Result<(), Error> {
	if usage_wrong() {
		eprintln!("Usage: [-abc] required_arg");
		process::exit(1);
	}
}

Additional Note

When a process using this library is spawned, the parent process calling exec() could potentially not provide the program name in argv[0]. In this case, we default to using the Rust style of "Error: {s}".

No runtime deps