Pretty print errors from `main() -> Result<(), failure::Error>`

Hi

I would like to control exactly how the error text loos like when returning a failure::Error from main() (fn main() -> Result<(), failure::Error> {}).

Can this be done somehow?

I know I can create another function that does all the heavy work and call that function from main(), match the error and print it the way I want. The question is if it can be done directly.

Thank you

1 Like

Can you give an example? Do you mean you don't like the "Error:" prefix before the error's message is displayed? That part is hardcoded in std. Once the Termination trait is stable, you can return a custom type with whatever output you like.

I am using nightly so I can use unstable stuff. Do you have details on how Termination trait can be used for this? I thought it's only to be used to control the exit code.

I am ok with the Error: prefix. I want to be able to control how the error context and other additional info (specific to failure crate) is printed. Also, I would like to add the backtrace if the user is using a cli flag.

For example, by default the error looks like this:

Error: KubeclientError("Kubernetes API error: pods \"serve-hostname-86bc9d96dc-tztt\" not found")

foo bar

The foo bar thing is the additional context() passed to the error

Result is for control flow. To give users a pretty message you need to write the renderer code.
main() -> Result<..> is best for demos where your not targeting anyone but the developer.

3 Likes

It has to return the exit code, but you can do whatever you want inside Termination::report. You can see how Result<(), E> does it. But you said you're ok with the "Error:" prefix, so I don't think you really need Termination anyway.

You could conceivably add a newtype over failure::Context, although there may be a better way. But something like:

struct PrettyPrinter(failure::Context<MyContext>);

impl fmt::Debug for PrettyPrinter {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Custom text here ...")?;
        // Would come from elsewhere
        let include_backtrace = true;
        match (include_backtrace, self.0.backtrace()) {
            (true, Some(bt)) => writeln!(f, "Backtrace: {}", bt)?,
            (true, _) => writeln!(f, "No backtrace available")?,
            _ => {}
        }
        writeln!(f, "context: {} {}", self.0.get_context().0, self.0.get_context().1)?;
        Ok(())
    }
}

#[derive(Debug)]
struct MyContext(String, String);

impl fmt::Display for MyContext {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.0, self.1)
    }
}

impl From<failure::Context<MyContext>> for PrettyPrinter {
    fn from(e: failure::Context<MyContext>) -> Self {
        PrettyPrinter(e)
    }
}

fn fallible() -> Result<(), failure::Context<MyContext>> {
    Err(failure::Error::from(std::io::Error::new(std::io::ErrorKind::Other, "boom")).context(MyContext("foo".into(), "bar".into())))
}

fn main() -> Result<(), PrettyPrinter> {
    fallible()?;
    Ok(())
}

I'm not sure if there's a more appropriate way to deal with Context or to get access to one - that's more of a failure question.

cc @withoutboats for better suggestions

2 Likes

Putting if let Err in main takes less code than any wrapper or implementation or Termination trait.
Really that's the shortest code that can't be beaten on brevity and clarity of intent.

4 Likes

@maximih did say

I think this is a question of "is there a different approach" - whether someone feels that approach is better or not is a different question :slight_smile:.

Yes, I'm questioning the question here :slight_smile: OP knows the solution, but IMHO incorrectly rejects it.

The desire to format that output was the first thing that came up in the RFC. It feels like something that should be done, so "don't bother" is a very counter-intuitive answer.

But it goes like this:

  1. You want to pretty-print output from main
  2. It's almost, but not quite what you want. Maybe it prints with "error:" prefix, but you wanted "Error!", or you use failure and want to list causes. Or you wanted to return exit status 7 when errors happen on Tuesday.
  3. so you want to replace the default pretty print logic

And when you reach the point 3 you end up defining newtypes, implementing traits and piling all that "elegant" complexity to inject the same code you'd put in if let Err. The wrapper function doesn't give the feeling of using the right language feature, but it is the right way to do it.

1 Like

In general, I'm in agreement with you. It's hard to suggest things in a vacuum, when one doesn't have enough context. For instance, you might have a type that's already used for printing stuff somehow - you may want to reuse it as the return value in main() (once Termination is stable), rather than refactoring rendering into a standalone function and/or wrapping into a Result.

I think it's worthwhile to demonstrate various techniques, and then let others judge which is best for their case. Particularly when there's little context and a person is asking precisely for alternatives. The alternative may be worse in a particular case but better in another, but one can only judge that if they know the alternative to begin with :slight_smile:.

But I think I'm digressing this thread so ...

1 Like

I've written a small crate that takes exactly this approach and provides a newtype wrapper around failure::Error - exitfailure. Lets me just use a main() -> Result<(), ExitFailure> and have the pretty printing taken care of.

It's nothing fancy but it saves me writing all that boilerplate for each project.

7 Likes