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?
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.
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 .
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.
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.
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:
%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:
xerrors
is not an option.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.%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.
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.
If an operand implements the Formatter interface, it will be invoked. Formatter provides fine control of formatting.
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:
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).
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:
Format()
method, and if it exists, they'll call it.Error()
method, and if it exists, they'll call it.String()
method, and if it exists, call it.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
valuesWhile 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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With