2 stable releases
1.0.1 | Aug 1, 2023 |
---|
#513 in Command-line interface
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}"
.