What is the difference between %w and %v in fmt.Errorf?
Question 296MediumGo 1.22 to 1.25
Both produce the same message text. %w also wraps: the returned error has an Unwrap() error method (or Unwrap() []error when there are several %w verbs, Go 1.20+), so errors.Is and errors.As can see the original. %v flattens the cause into a string and cuts the chain.
var ErrNotFound = errors.New("not found")
e1 := fmt.Errorf("get user 42: %w", ErrNotFound)
e2 := fmt.Errorf("get user 42: %v", ErrNotFound)
fmt.Println(errors.Is(e1, ErrNotFound)) // true
fmt.Println(errors.Is(e2, ErrNotFound)) // false
fmt.Println(e1.Error() == e2.Error()) // true
e3 := fmt.Errorf("a: %w, b: %w", io.EOF, fs.ErrClosed) // Go 1.20+
fmt.Println(errors.Is(e3, fs.ErrClosed)) // true
Gotchas: %w works only in fmt.Errorf. Its operand must implement error, otherwise you get %!w(...) in the text (and go vet flags it). Choosing %w is an API decision: callers can now depend on the wrapped error. Use %v when the cause is an implementation detail you don't want to expose.
More on Error Handling & panics
- Q294Why does Go treat errors as values instead of using exceptions? What are the trade-offs?
- Q295What does this print, and why?
errors.New("x") == errors.New("x") - Q297How does
errors.Iswork internally, and how can a custom type change its matching? - Q298Explain
errors.As. Why must the target be a pointer, and what happens if you get it wrong? - Q299What is
errors.Join? How do multi-errors interact witherrors.Is,errors.Asanderrors.Unwrap? - Q300Compare sentinel errors, typed errors and opaque errors. When would you use each?