News for hackers. Stuff that matters.
Re:Go’ing Insane: Endless Error Handling by nicoburns on Tuesday September 14, 2021 @07:48AM (#28523262)
If you want to manually wrap/change the error somehow then the pattern would be something like:

     foo().map_err(|err| WrapperError::new(err))?
For context, typically you would use a library like `anyhow` which allows you to do:

     foo().context("Context string here")?
or

     foo().with_context(|| format!("dyanmically generated context {}", bar))?
I don't know of any pre-built solutions for logging, but you could easily add a method to Result yourself that logged like:

    foo().log("log message")?
or something like:

    foo().log(logger, 'info', "log message")?
The code to do this would be something like:

    use log::error;
    
    trait LogError {
        fn log(self, message: impl Display) -> Self;
    }
    
    impl<T, E> LogError for Result<T, E> {
        fn log(self, message: impl Display) -> Self {
            error!("{}", message);
            self
        }
    }
Then you'd just need to import the `LogError` error trait at the top of your file to make the `.log()` method available on errors.
Re:Go’ing Insane: Endless Error Handling by samhw on Tuesday September 14, 2021 @08:27AM (#28523685)
Nice to see so many people mentioning `map_err`. This is exactly what I've done when working with Rust (I have professional experience with both, slightly more with Go) and it's a dream compared with Go's error handling.
Re:Go’ing Insane: Endless Error Handling by politician on Tuesday September 14, 2021 @09:00AM (#28524069)
What if you want to log, then emit some time series data?

Is chaining+traits+wrappers+map_err truly less complex than a conditional:

  if BAD {
    Log()
    Metric1()
    Metric2()
  }
Re:Go’ing Insane: Endless Error Handling by nicoburns on Tuesday September 14, 2021 @09:13AM (#28524235)
If you're doing something more complex/involved in order to handle a particular error, then no I think a conditional/match is fine. It's the common case when you're not doing any special that the boilerplate becomes overwhelming.

Having said that, if you're outputting the same metrics for every error then a method that does this for you makes a lot more sense than repeating yourself all over the place.

Re:Go’ing Insane: Endless Error Handling by chrismorgan on Tuesday September 14, 2021 @09:22AM (#28524390)
And this is the beauty of chaining and extension traits: you can define your own “shorthands” without much trouble, because it’s all in the normal type system.

Remember also that .map_err() need not be significantly different from that if block; it can be essentially just a slightly different spelling:

  let data = some_call().map_err(|e| {
      log();
      metric1();
      metric2();
      e
  })?;
But I would draw your attention in all this to the fact that you’re dealing with algebraic data types, enums with data attached to the variants; this isn’t just some nullable data value and nullable error where you can say `if error { … }`; in order to get at the data, you need to handle the error. This has profound implications that, once you’re used to it, will shape your coding style and techniques even when you return to other languages.

On things like metrics, I’d also mention that sometimes you may be better served by an RAII pattern, which can be made to be the rough equivalent of a defer statement in Go. Especially if you’re pairing start/end metrics, RAII can be particularly good at that.

Re:Go’ing Insane: Endless Error Handling by wavemode on Thursday September 16, 2021 @07:23AM (#28550326)
The commenter you're replying to is simply showing how logging and context and other things can be done in conjunction with the question mark syntax sugar. If you don't want to use the question mark at all, you're still free to explicitly pattern match on the Result object and do whatever complex thing you want with the error object before returning it.