Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Format errors in Go - %s %v or %w

Both %s and %v can be used to format errors in Go, and there seems to be no functional difference, at least superficially.

We see both in Go's own tools.

In cmd/go/internal/get/path.go: return fmt.Errorf("malformed import path %q: %v", path, err)

In cmd/go/internal/list/list.go: base.Fatalf("%s", err)

Should I ever prefer one over the other?

like image 549
loopbackbee Avatar asked Apr 18 '20 01:04

loopbackbee


People also ask

What is %s in Golang?

The %s expects a string value and the %d an integer value. res := fmt.Sprintf("%s is %d years old", name, age) The fmt. Sprintf function formats a string into a variable. $ go run fmt_funs.go Jane is 17 years old Jane is 17 years old.

What does %w mean in Golang?

Use %w instead of %v or %s : As of Go 1.13 (or earlier if you use golang.org/x/xerrors), you can use the %w verb, only for error values, which wraps the error such that it can later be unwrapped with errors. Unwrap , and so that it can be considered with errors.Is and errors.As .

What is %+ V in Golang?

when printing structs, the plus flag (%+v) adds field names. %#v a Go-syntax representation of the value. %T a Go-syntax representation of the type of the value. %% a literal percent sign; consumes no value.

What is Golang error type?

error is a built-in interface type in Go. An error variable represents any value that can describe itself as a string . The following is the error interface declaration: type error interface { Error() string.


2 Answers

Should I use %s or %v to format errors?

TL;DR; Neither. Use %w in 99.99% of cases. In the other 0.001% of cases, %v and %s probably "should" behave the same, except when the error value is nil, but there are no guarantees. The friendlier output of %v for nil errors may be reason to prefer %v (see below).

Now for details:

Use %w instead of %v or %s:

As of Go 1.13 (or earlier if you use golang.org/x/xerrors), you can use the %w verb, only for error values, which wraps the error such that it can later be unwrapped with errors.Unwrap, and so that it can be considered with errors.Is and errors.As.

The only times this is inappropriate:

  1. You must support an older version of Go, and xerrors is not an option.
  2. You want to create a unique error, and not wrap an existing one. This might be appropriate, for instance, if you get a Not found error from your database when searching for a user, and want to convert this to an Unauthorized response. In such a case, it's rare that you'd be using the original error value with any formatting verb, though.

Okay, so what about %v and %s?

The details for how %s and %v are implemented are available in the docs. I've highlighted the parts relevant to your question.

  1. If the operand is a reflect.Value, the operand is replaced by the concrete value that it holds, and printing continues with the next rule.

  2. If an operand implements the Formatter interface, it will be invoked. Formatter provides fine control of formatting.

  3. If the %v verb is used with the # flag (%#v) and the operand implements the GoStringer interface, that will be invoked.

    If the format (which is implicitly %v for Println etc.) is valid for a string (%s %q %v %x %X), the following two rules apply:

  4. If an operand implements the error interface, the Error method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

  5. If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

To summarize, the fmt.*f functions will:

  1. Look for a Format() method, and if it exists, they'll call it.
  2. Look for a Error() method, and if it exists, they'll call it.
  3. Look for a String() method, and if it exists, call it.
  4. Use some default formatting.

So in practice, this means that %s and %v are identical, except when a Format() method exists on the error type (or when the error is nil). When an error does have a Format() method, one might hope that it would produce the same output with %s, %v, and err.Error(), but since this is up to the implementation of the error, there are no guarantees, and thus no "right answer" here.

And finally, if your error type supports the %+v verb variant, then you will, of course, need to use that, if you desire the detailed output.

nil values

While it's rare to (intentionally) call fmt.*f on a nil error, the behavior does differ between %s and %v:

%s: %!s(<nil>)
%v: <nil>

Playground link

like image 121
Flimzy Avatar answered Nov 10 '22 15:11

Flimzy


Use %v for an error val.

if err != nil {
    return fmt.Errorf("pack %v: %v", name, err)
}

But, In Go 1.13, the fmt.Errorf function supports a new %w verb. When this verb is present, the error returned by fmt.Errorf will have an Unwrap method returning the argument of %w, which must be an error. In all other ways, %w is identical to %v.

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("pack %v: %w", name, err)
}

Places where you need to differentiate between %w and %v:

Read comments in the codeblock

f, err := os.Open(filename)
if err != nil {
    // The *os.PathError returned by os.Open is an internal detail.
    // To avoid exposing it to the caller, repackage it as a new
    // error with the same text.
    //
    //
    // We use the %v formatting verb, since
    // %w would permit the caller to unwrap the original *os.PathError.
    return fmt.Errorf("%v", err)
}

Read: For an error, when should I switch to w


Also, the built-in error interface allows Go programmers to add whatever information they desire. All it requires is a type that implements an Error method

Example:

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

So, mostly most examples have a similar type of implementation where err has an Error method which returns string for which you can use %s

like image 32
shmsr Avatar answered Nov 10 '22 15:11

shmsr