Hi Mike, thank you so much for sharing your thoughts and all those 
resources with concrete examples. Those really help me to understand how 
errors in go work and how it is different from errors in Java. 

Based on what you've shared, here's how I understand it:
In essence, because an error in Go is just a value, there’s no built-in way 
to signal which specific errors might be returned except through 
documentation. However, since an error is a value, we can also enrich it 
with additional context like user-friendly messages, stack traces, and more.
Plus, sentinel errors are one way to expose API errors and once we exposed 
those to API users, it is very important to stick to it and not to change.

And I found the below article very interesting. Please check it out when 
you have time.
https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully

Thank you again for your sharing. 

2025년 2월 4일 화요일 오전 3시 2분 37초 UTC+9에 Mike Schinkel님이 작성:

On Feb 3, 2025, at 6:01 AM, Byungjun You <schi...@gmail.com> wrote:

In Java, checked exceptions allow developers to define in advance which 
exceptions a function can throw. However, it seems that Go does not have 
such a feature. Would it be considered a best practice in Go to document 
the possible errors that an API function can return? Additionally, what are 
some best practices that API providers can follow to help API users handle 
errors effectively?

In Go, errors are values[1] meaning most in the Go community frown on 
try-catch style exception handling as in Java. 

Each function in Go which can generate an error will typically return that 
error as an additional value; e.g.:

func (d Data) getValue() (value any, err error) {
   if d.value == nil {
      return nil, errors.New("value as not been set")
   }
   if reflect.ValueOf(d.value).IsZero() {
      return nil, errors.New("value is empty")
   }
   return value,err
}

Then getValue() would be called like this:

value, err := data.getValue()

See https://go.dev/play/p/PciRk_t_UaV for full working example.

In some cases — though not as many as I would like — Go developers often 
create sentinel values that might look like this:

var (
  ErrValueNotSet  = errors.New("value as not been set")
  ErrValueIsEmpty = errors.New("value is empty")
)

Then callers can check them using errors.Is() like so:

  if errors.Is(err, ErrValueNotSet) {...}
  if errors.Is(err, ErrValueIsEmpty) {...}

See https://go.dev/play/p/pamY1pcAoVP for a full working example.

A common sentinel error value from the Go std lib is io.EOF[2]

However, AFAIK, there is no way programmatically to determine which errors 
a Go function might return at this time. It would be nice if there were 
some built-in mechanism for doing that using the reflect package, but I 
would not expect that to be added as I cannot imagine how it could be 
implemented without slowing down compilation. Unfortunately.

As for best practices, IMO that would be to define sentinel error variables 
instead of errors created inline with errors.New() or fmt.Errorf() for all 
errors that your function may return, and then use errors.Join() to join 
with any errors returned by a function your function called before 
returning to the caller. Then document your sentinel error variables and 
commit to not renaming or removing them in future versions of your API.

Does that answer your question?

-Mike
[1] https://go.dev/blog/errors-are-values
[2] 
https://github.com/golang/go/blob/beea7c1ba6a93c2a2991e79936ac4050bae851c4/src/io/io.go#L44

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/golang-nuts/f3871600-0bd2-4a91-b94b-f24fa7828a40n%40googlegroups.com.

Reply via email to