Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2 error values #29934

Open
jba opened this Issue Jan 25, 2019 · 246 comments

Comments

Projects
None yet
@jba
Copy link
Contributor

jba commented Jan 25, 2019

This issue is for discussion of our Go 2 error values proposal, which is based on our draft designs for error information and formatting.

This proposal will follow the process outlined in the "Go 2, here we come" blog post: we will have everything ready to go and checked in at the start of the Go 1.13 cycle (Feb 1), we will spend the next three months using those features and soliciting feedback based on actual usage, and then at the start of the release freeze (May 1), we will make the "launch decision" about whether to include the work in Go 1.13 or not.

@gopherbot gopherbot added this to the Proposal milestone Jan 25, 2019

@gopherbot gopherbot added the Proposal label Jan 25, 2019

@mvdan

This comment has been minimized.

Copy link
Member

mvdan commented Jan 25, 2019

Assuming this is accepted and implemented before generics, would the design be adapted in a potential redesign of std with generics?

@mpvl

This comment has been minimized.

Copy link
Member

mpvl commented Jan 25, 2019

@mvdan We think the design not using generics resulted in a slightly nicer API in the end. So we will probably not do so. That said, it can't be ruled out there will be additional API using generics.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 25, 2019

Errors constructed with errors.New and fmt.Errorf will display differently with %+v

Could a change in output break consumers which compare it with a hard-coded string, e.g. test code?

@gopherbot gopherbot added the Go2 label Jan 25, 2019

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 26, 2019

Errors constructed with errors.New and fmt.Errorf will display differently with %+v

Could a change in output break consumers which compare it with a hard-coded string, e.g. test code?

Conceivable, but since the fmt package currently treats %+v identically to %v for any type which implements the error interface there isn't a strong reason for users to use the former to format errors at the moment.

@Sherlock-Holo

This comment has been minimized.

Copy link

Sherlock-Holo commented Jan 27, 2019

I wonder why xerrors.Errorf("new error: %w", baseErr) %w must at the end, I think when print the error, it should be

baseErr: some error

not

some error: baseErr

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 27, 2019

Agreed it's odd that the format string is an output-type controller, rather than a flag in the verb like %$v.

Also, have the authors considered providing this, which is conspicuously absent in package fmt:

func Error(a ...interface{}) error

This works like errors.New(fmt.Sprint(a, b))
Just as Errorf works like errors.New(fmt.Sprintf(s, a, b))

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 27, 2019

Agreed it's odd that the format string is an output-type controller, rather than a flag in the verb like %$v.

What are the arguments for a flag over a verb? I don't see a strong argument for one over the other and %w has some mnemonic value, but perhaps I'm missing something.

func Error(a ...interface{}) error

I think this is orthogonal to the rest of the proposal, but it doesn't seem unreasonable. On the other hand, I don't think I've ever felt the absence of this function.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 28, 2019

I'm referring to this output-type control via the format string: "if ... the format string ends with : %s, : %v, or : %w, then the returned error will implement FormatError." Why not a flag for %s & %v?

The easiest way to print an error is: fmt.Println("this happened: ", err)
The easiest way to make an error should be: fmt.Error("this happened: ", err)

@jba

This comment has been minimized.

Copy link
Contributor Author

jba commented Jan 28, 2019

Why not a flag for %s & %v?

The only existing flags that don't have a meaning for %s and %v are space and 0. But those both feel wrong, so we'd need to make up a new flag. That means one could potentially use that flag with any verb, but it would be meaningless except for %s and %v. That feels wasteful—flags and verbs should combine orthogonally to (almost) always produce a useful result (with space and 0 themselves being the notable exceptions).

@ericlagergren

This comment has been minimized.

Copy link
Contributor

ericlagergren commented Jan 28, 2019

To be clear: does this mean each call to fmt.Errorf will collect a stack trace?

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 28, 2019

To be clear: does this mean each call to fmt.Errorf will collect a stack trace?

A single stack frame, not a full trace.

@tandr

This comment has been minimized.

Copy link

tandr commented Jan 28, 2019

To be clear: does this mean each call to fmt.Errorf will collect a stack trace?

A single stack frame, not a full trace.

Would it be possible to make it "adjustable" somehow? In our home-made logging wrapper we have noticed that we have to pull a couple or 3 frames to get to "the caller of interest", so printout looks more or less pointing to the place where logwrap.Print(err) function was called.
If this is becoming a part of the standard, in case when wrapper (or utility method) is created to simplify an error creation, I would like to report the place where that method was called from, and not the utility method itself.

We did it by (arguable not so elegant) way as adding a parameter "how big is the stack offset" to the logging function, but I am not sure if that's the only option here. Adding special format symbol for stack, with number of frames interesting might be one of possible approaches.

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 28, 2019

The intended usage is that additional frames be added as necessary by annotating or wrapping the error, possibly with additional context.

For example,

func inner() error { return errors.New("inner error") }
func outer() error { return fmt.Errorf("outer error: %w", inner()) }

fmt.Fprintf("%+v", outer())
// outer error:
//     /path/to/file.go:123
//   - inner error:
//     /path/to/file.go:122

To attach information about where a utility function was called from, you do so in the same way as today: By annotating the error with additional information.

We considered capturing a full stack rather than a single frame, but think the single-frame approach has some advantages: It's light-weight enough to be feasible to do for every error and it deals well with error flows that pass between goroutines.

That said, this proposal makes it easy for error implementations to add whatever detail information they want in a compatible way, be it full stacks, offset stack frames, or something else.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 28, 2019

[the flag] would be meaningless except for %s and %v. That feels wasteful—flags and verbs should combine orthogonally to (almost) always produce a useful result (with space and 0 themselves being the notable exceptions).

Erm, I think you've refuted your own argument by raising exceptions :-)

: %s is magical, invisible, and readily broken without compiler complaint. And no other format string has similar effect. That's not Goish to my eye.

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 29, 2019

: %s is magical, invisible, and readily broken without compiler complaint. And no other format string has similar effect. That's not Goish to my eye.

I'm not certain if you're making an case about verbs vs. flags (%v/%w vs. %v/%$v) or about using fmt.Errorf to annotate/wrap errors in general. Could you clarify your understanding of the proposal and what you're suggesting instead?

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 29, 2019

As I said above, I'm referring to this output-type control in the format string:

if ... the format string ends with : %s, : %v, or : %w, then the returned error will implement FormatError.

The control should be a flag e.g. %$s & %$v, and not the contents of the format string.

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 29, 2019

The control should be a flag e.g. %$s & %$v, and not the contents of the format string.

Thanks; I think I understand you now.

The current design of applying special-case handling in fmt.Errorf to a : %v et al. suffix is informed by a couple of factors.

Firstly, we want to permit existing code to take as much advantage of the new error formatting features as possible. So, for example, errors.New and fmt.Errorf now capture the caller's stack frame by and print this information in detail mode. Existing code that creates errors does not need to change to include this information. But what about annotation of errors? Consider the following:

func f1() error { return errors.New("some error") }
func f2() error { return fmt.Errorf("f2: %v", f1()) }

func main() {
  fmt.Printf("%+v", f2())
}

We want this to print:

some error:
    main.go:1
  - f2:
    main.go:2

But if annotation is only enabled with a special format directive like %$v, then this will instead print:

some error: f2
    main.go:2

The innermost stack frame is lost.

The other consideration is that error annotation is linear. The errors.Formatter interface allows an error to format itself and return the next error to format. By design, it does not provide a way for an error to interleave its output with that of another error. This limitation makes the formatted output more consistent and predictable, but it means that there is no way to insert a hypothetical %$v into the middle of a format string:

// What would this error's `FormatError` method do?
return fmt.Errorf("some information about error: %$v (with some more information)", err)

These two considerations led to the current design of automatically annotating errors produced by fmt.Errorf calls that follow the most common pattern of adding information to an error, but only when the original error appears at the end of the string.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 29, 2019

Re the stack frame, annotation could be enabled by any use of an error argument with Errorf(), instead of a magical, brittle format string.

there is no way to insert a hypothetical %$v into the middle of a format string

Would a format string "abc %v 123" work as expected given annotation enablement on use of an error argument? If not, I believe you need to rethink this...

@jba

This comment has been minimized.

Copy link
Contributor Author

jba commented Jan 29, 2019

Would a format string "abc %v 123" work as expected given annotation enablement on use of an error argument?

No; we will only treat an error specially if the format string ends with ": %s" or ": %v".

If not, I believe you need to rethink this...

Can you explain why? Our goal with this feature is to instantly and painlessly bring the new formatting into most existing Go code. We'd rethink that if, for example, it turned out that most existing calls to fmt.Errorf that included an error did not put the error at the end with ": %s" or ": %v". If that is true, we'd like to know.

As Damien pointed out, if you do like to put errors in the middle of your format strings, you're going to have bigger problems with our proposal than its fmt.Errorf behavior. Even if you roll your own formatting with the FormatError method, you're going to have a hard time getting wrapped errors to display in the middle.

One more point: while we do expect people to continue to write fmt.Errorf calls that wrap errors, we also encourage custom error-creating functions that build specific errors and might incorporate other features, like frame depth (as in @tandr's request). E.g.

type MyError ...
func MyErrorf(wrapped error, frameDepth int, format string, args ...interface{}) *MyError
@tandr

This comment has been minimized.

Copy link

tandr commented Jan 29, 2019

No; we will only treat an error specially if the format string ends with ": %s" or ": %v".

My concern here would be that
a) not every message fits this format, and
b) not every message is going to be in English.

"Something happened: This is What" is an ok way to produce a short human-readable error, but... Even in English, if I would want some text explaining "What to do now?" (remediation suggestion, "contact support" etc), that : %v might not be the last item in the formatting string. And the whole "at the end" logic will be completely broken for languages that are using Right-to-Left writing order (Arabic, Hebrew, Farsi, Urdu etc.)

Would it be easier to introduce a special formatter "print this as 'error'" and a modifier to make it "print this as an error with a stack"? I think there are enough letters in English alphabet left to cover one more case.

Also, if I may ask - no magic, please.

@glibsm

This comment has been minimized.

Copy link

glibsm commented Jan 29, 2019

The Unwrap function is a convenience for calling the Unwrap method if one exists.

// Unwrap returns the result of calling the Unwrap method on err, if err implements Unwrap.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error

It would also be nice to have a standard helper that can get to the root cause, i.e. the last error in the chain.

Similar to https://godoc.org/github.com/pkg/errors#hdr-Retrieving_the_cause_of_an_error.

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 29, 2019

There are two points here, and it's worth considering them independently. The first is the manner in which error annotations compose.

The proposal adds a new errors.Formatter interface which errors may optionally implement. This interface provides two main pieces of functionality: Error detail in which an error may provide additional information when formatted with the %+v directive, and wrapping in which one error may add additional information to another.

A primary goal of the errors.Formatter interface is to allow different error implementations to interoperate. You should be able to create an error with errors.New, add some information to it with "github.com/pkg/errors".Wrap, add more information to that with "gopkg.in/errgo.v2/errors".Wrap, and get reasonable behavior.

Detail formatting of an error (%+v) produces multi-line output, with each step in the error chain represented as a section of one or more lines. For example:

could not adumbrate elephant:  - these three lines are the outermost error
    (some additional detail)   |
    labrynth.go:1986           /
  - out of peanuts             - and these two lines are the innermost error
    elephant.go:15             / 

Displaying a chain of errors in a consistent fashion requires making certain decisions about formatting: Do we print errors from the outermost to innermost, or vice-versa? How do we indicate boundaries between errors? If we leave these formatting decisions up to individual error implementations, we are likely to have situations where, for example, part of an error chain is printed inner-to-outer and another part is outer-to-inner; very confusing and not a good user experience.

To avoid confusion and simplify the work required to write a custom error type, we put the formatting of the error chain under the control of the error printer. Errors provide three pieces of information: The error text, detail text, and the next error in the chain. The printer does the rest. So, for example, the outermost error in the above example might have an ErrorFormatter implementation like:

func (e errType) FormatError(p Printer) (next error) {
  p.Print("could not adumbrate elephant") // error text
  if p.Detail() {
    p.Print("(some additional detail)") // error text only visible in detail (%+v)
    p.frame.FormatError(p) // print stack frame as detail
  }
  return e.wrappedErr // return the next error in the chain
}

Note again that an important property of this design is that an error doesn't choose how to format the error that it wraps; it returns that error to the printer, which then determines how to format the error chain. Not only does this simplify the error implementation and ensure consistency, but this property is important for localization of errors in RTL languages (as mentioned by @tandr), because it permits a localizing printer to adjust the output order as appropriate. (Most users will print errors using the fmt package, but note that this design deliberately allows for other implementations of errors.Printer.)

Given the above, the question then is how to make it simple for users to produce errors that satisfy these interfaces, which is where we get to the special handling of a suffix : %v. It will be helpful if critiques of this proposal are clear about whether a comment is about the above formatting interfaces, or fmt.Errorf specifically.

@neild

This comment has been minimized.

Copy link
Contributor

neild commented Jan 29, 2019

It would also be nice to have a standard helper that can get to the root cause, i.e. the last error in the chain.

The equivalent in this proposal is the errors.Is function:

if errors.Is(err, io.EOF) { ... }

We believe this has several nice properties over a function returning the last element in a chain:

  • Harder to accidentally discard context (e.g., return errors.Cause(err)).
  • An error can implement an Is(target error) bool) method to declare that it is equivalent to another error.
  • Nicely analogous to errors.As, which converts an error to a type.
@velovix

This comment has been minimized.

Copy link

velovix commented Jan 29, 2019

I like the motivation of the new Errorf behavior being a way to grandfather in old code. Near as I can tell, it could only make old code better. However, I'm less into the idea of Errorf being the main way to wrap errors. I could see programmers writing some error handling logic and, due to force of habit, accidentally writing fmt.Errorf("context: %v", err) instead of fmt.Errorf("context: %w", err) and missing out on potentially valuable information or effecting the intended error handling path. Since errors often find themselves in uncommon circumstances, it's possible to miss problems like these until an error in production occurs and you have less diagnostic information than you were hoping. For that reason, I think having a stronger, more compiler friendly wrapping solution available makes sense. Maybe something like github.com/pkg/errors.Wrap(err, message)?

To be a bit more concrete, I have more than once found myself in a similar situation with Python, where I raise an exception using f-strings but forget to put the "f".

raise RuntimeError("oh no! Something happened: {important_information}")

... instead of ...

raise RuntimeError(f"oh no! Something happened: {important_information}")

And since there's nothing syntactically wrong with what I did, I might not know until later when I need important_information that I've made a mistake.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Jan 29, 2019

Our goal with this feature is to instantly and painlessly bring the new formatting into most existing Go code.

Although that seems like a laudable goal, modifying the way existing code creates output is inherently risky. I suspect most of us would rather have an opt-in mechanism. Let project owners trial a feature and switch over (via go fix) when convenient for them.

IOW, new error-construction functions are needed, which always return errors.Formatter.

@PieterD

This comment has been minimized.

Copy link
Contributor

PieterD commented Jan 31, 2019

While the fmt.Errorf changes to wrap the error is neat and conserves backwards compatibility with previous versions, a slightly nicer option going forward might be good?

When using pkg/errors, errors.Wrapf(err, "error reticulating spline %d", splineID) is the most used pattern by far. If that is also introduced, both of the most common patterns are maintained (aside from a switch from %v to %w in fmt.Errorf, which might cause trouble if one is missed.)

@go101

This comment has been minimized.

Copy link

go101 commented Mar 13, 2019

A little off topic. Maybe Go needs an annotation alike feature to specify type properties.
For example,

  • we can specify the property of the type of "frames" as "its values are all equal with each other".
  • we can specify a type as no-copy type.
  • ...
@go101

This comment has been minimized.

Copy link

go101 commented Mar 13, 2019

or annotations for struct fields.

@IngCr3at1on

This comment has been minimized.

Copy link

IngCr3at1on commented Mar 13, 2019

@go101 you can (and in fact should) easily write package documentation for externally available struct values which is already fully supported by godoc. I'm not sure another annotation method is needed?


edit: sorry I reread that comment again and you are talking about slightly different annotation (to be read by the runtime instead of by the developer) I apologize for confusion...

I'm just wondering if struct tags might work for that as well...

@Sherlock-Holo

This comment has been minimized.

Copy link

Sherlock-Holo commented Mar 14, 2019

when use log.Printf("%+v") to print xerrors stack, can the sequence follow panic sequence?

@go101

This comment has been minimized.

Copy link

go101 commented Mar 14, 2019

@IngCr3at1on

I'm just wondering if struct tags might work for that as well...

I don't know. I have never seen examples of struct field tags serve for compiler/runtime.

gopherbot pushed a commit that referenced this issue Mar 20, 2019

os: make errors.Is work with ErrPermission et al.
As proposed in Issue #29934, update errors produced by the os package to
work with errors.Is sentinel tests. For example,
errors.Is(err, os.ErrPermission) is equivalent to os.IsPermission(err)
with added unwrapping support.

Move the definition for os.ErrPermission and others into the syscall
package. Add an Is method to syscall.Errno and others. Add an Unwrap
method to os.PathError and others.

Updates #30322
Updates #29934

Change-Id: I95727d26c18a5354c720de316dff0bffc04dd926
Reviewed-on: https://go-review.googlesource.com/c/go/+/163058
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
@gopherbot

This comment has been minimized.

Copy link

gopherbot commented Mar 20, 2019

Change https://golang.org/cl/168438 mentions this issue: reflect: ignore errors.Frame in DeepEqual

@mpvl

This comment has been minimized.

Copy link
Member

mpvl commented Mar 20, 2019

@Sherlock-Holo I'm not entirely clear what you mean, but printing errors relies on package fmt, which recovers panics and always prints something. So log.Printf should not panic if, say, an implementation of FormatError panics.

@Sherlock-Holo

This comment has been minimized.

Copy link

Sherlock-Holo commented Mar 21, 2019

func do() error {
	return xerrors.New("err 1")
}

func main() {
	log.Printf("%+v",xerrors.Errorf("err 2: %w", do()))
}

will print

2019/03/21 09:36:24 err 2:
    main.main
        /home/sherlock/go/src/go-learn/test3.go:14
  - err 1:
    main.do
        /home/sherlock/go/src/go-learn/test3.go:10
func do() error {
	panic("err 1")
	// return xerrors.New("err 1")
}

func main() {
	do()
	// log.Printf("%+v",xerrors.Errorf("err 2: %w", do()))
}

will print

panic: err 1

goroutine 1 [running]:
main.do(...)
	/home/sherlock/go/src/go-learn/test3.go:4
main.main()
	/home/sherlock/go/src/go-learn/test3.go:9 +0x3a

I like the second print style, when in log, I want to see which function has error at first, and then find out the head of error chain. Now xerrors print the head of error chain at last line, print the function which log the error at first line. It is a bit diffcult to debug the codes

@jba

This comment has been minimized.

Copy link
Contributor Author

jba commented Mar 22, 2019

The vet check for errors.As is at https://go-review.googlesource.com/c/tools/+/168938.

@go101

This comment has been minimized.

Copy link

go101 commented Mar 23, 2019

I don't if it is a bug. If it is not, we can make use of this fact to ignore the frame field in comparisons by using reflet.DeepEqual:

package main
import "fmt"
import "reflect"

type Frame struct {
	self   *Frame
	frames [1]uintptr
}

type errorString struct {
	s     string
	err   error
	frame Frame
}

func main() {
	var x, y errorString
	x.frame.self = &x.frame
	y.frame.self = &y.frame
	fmt.Println("aa:", reflect.DeepEqual(x, y)) // true
	fmt.Println("bb:", x == y)                  // false
}

or even simpler

package main

import "fmt"
import "reflect"

type Dummy *Dummy

type errorString struct {
	s     string
	err   error
	frame Dummy
}

func main() {
	var x, y errorString
	x.frame = &x.frame
	y.frame = &y.frame
	fmt.Println("aa:", reflect.DeepEqual(x, y)) // true
	fmt.Println("bb:", x == y)                  // false
}
@go101

This comment has been minimized.

Copy link

go101 commented Mar 23, 2019

sorry, this doesn't work. I thought it wrongly. Just ignore it.

@go101

This comment has been minimized.

Copy link

go101 commented Mar 23, 2019

I thought it again. Maybe there still a hope.
But I am really not familiar with the runtime code, so just ignore this if the following code is a joke.

package main

import "fmt"
import "reflect"
import "unsafe"

type loop *loop

type Frame struct {
	self   loop
	frames [1]uintptr
}

func frame2loop(c *Frame) loop {
	c.self = &c.self
	return c.self
}

func loop2frame(l loop) *Frame {
	return (*Frame)(unsafe.Pointer(l))
}

type errorString struct {
	s     string
	err   error
	nocmp loop // ignore comparison
}

func main() {
	var x, y errorString
	x.nocmp = frame2loop(&Frame{});
	y.nocmp = frame2loop(&Frame{});
	loop2frame(x.nocmp).frames[0] = 123
	loop2frame(y.nocmp).frames[0] = 789
	fmt.Println(reflect.DeepEqual(x, y)) // true
	fmt.Println(x == y)                  // false
	fmt.Println(loop2frame(x.nocmp)) // &{0xc0000101e0 [123]}
	fmt.Println(loop2frame(y.nocmp)) // &{0xc0000101f0 [789]}
}

[edit]: it is proved that this trick is runtime unrelated. I just noticed that there is already a pr to ignore the Frame fields in deep comparisons. That method is much simpler than this one, and that one doesn't use unsafe mechanism, but it adds a new exception to the DeepEqual function implementation. Another difference is that pr implementation makes the Frame type which values are all deep equal to each other, but this implementation makes the values of the Frame type equal to each other only when they act as fields of errorString values.

@peterbourgon

This comment has been minimized.

Copy link
Member

peterbourgon commented Mar 23, 2019

I'm sorry I'm late to the party here, and please redirect me if this isn't the right place for this feedback.

I scanned xerrors for a Wrap/Wrapf-equivalent, and, finding none, was surprised to learn that the only way to create wrapped errors inline is via the "magic" behavior now granted to e.g.

return fmt.Errorf("error reticulating splines: %v", err)

I find this nonintuitive and would absolutely expect to see an explicit Wrap/Wrapf helper in the package.

@ericlagergren

This comment has been minimized.

Copy link
Contributor

ericlagergren commented Mar 23, 2019

@peterbourgon btw if you’re curious why they made that decision, it is discussed in this GitHub issue.

You just might have to do a lot of scrolling and ctrl+F. I literally cannot link you to the discussions from mobile because the thread is too long. 😄

@peterbourgon

This comment has been minimized.

Copy link
Member

peterbourgon commented Mar 23, 2019

@ericlagergren Yes, I saw the rationale. But I can count the number of times I've used Cause/Frame/Stackframe/Is/As/etc. on one hand, whereas I use Wrap/Wrapf ubiquitously, in essentially every program I write; an errors package feels incomplete without those helpers.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Mar 23, 2019

Your example code should probably be:

return fmt.Errorf("error reticulating splines: %w", err) // %w
@peterbourgon

This comment has been minimized.

Copy link
Member

peterbourgon commented Mar 23, 2019

@networkimprov No, I don't want to include the entire error stack in the error string. (I never want this, actually.)

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Mar 23, 2019

That's not what %w does. It makes the result usable with Is/As/Unwrap(). The default (yours) adds a stack frame (that's unavoidable). The frame is why reflect.DeepEqual() now fails for errors.

The stack only prints via %+v in a log format string.

I have argued #29934 (comment) that framed-error construction should get a new API, and that tooling could let projects update error construction as desired.

I forget how the planned automatic "upgrade" integrates with custom error types.

@ericlagergren

This comment has been minimized.

Copy link
Contributor

ericlagergren commented Mar 24, 2019

@peterbourgon Just making sure you (and anybody else new to this thread) didn’t accidentally miss it. 👍

@peterbourgon

This comment has been minimized.

Copy link
Member

peterbourgon commented Mar 25, 2019

@networkimprov

That's not what %w does. It makes the result usable with Is/As/Unwrap().

Oh, then I've misunderstood the proposal. I tried to parse it carefully; not great!

@JavierZunzunegui

This comment has been minimized.

Copy link

JavierZunzunegui commented Mar 25, 2019

While Formatter is an optional implementation, I was under the impression that a call to FormatError would result in the Printer being used in all wrapped Formatter errors, and only the non-Formatter errors being displayed via Errror. That is not the case though, the first non-Formatter breaks the chain with regards to Printer. An example to demonstrate my point:

say FError implements Formatter
say NotFError does not implement Formatter
Both implement Wrapper
Assume the wrapping order is FError-0 wraps NotFError-0 wraps FError-1 wraps ...
Then the Printer p provided to FError-0. FormatError formats that error, but can't be passed on to NotFError-0 since it does not have a FormatError method, which must fall back to NotFError-0.Error() and so calls FError-1.Error() or FError.FomatError with whatever default Printer it is set to use, but critically not p.

My point being using custom Printers seems to rely on all errors implementing Formatter, one that doesn't breaks the chain. That seems like a flaw in the design.

@JavierZunzunegui

This comment has been minimized.

Copy link

JavierZunzunegui commented Mar 28, 2019

I have produced a second version of an alternative proposal for error wrapping. Please see #31111

At a high level, compared to the original proposal, this one:

  • has no requirement on error types (no Unwrap() error or equivalent)
  • allows for custom error conversion to string in a more powerful manner
  • has no automatic migration to wrapping form (code is not immediately using wrapping, no %w or equivalent)
  • transparently ads full stack information to wrapped errors, as opposed to frames
  • compile-time safe implementation with few gotchas
  • can compare errors without requiring modification of reflect.DeepEqual

Thanks to all who provided feedback to the first version.

@gopherbot

This comment has been minimized.

Copy link

gopherbot commented Mar 28, 2019

Change https://golang.org/cl/170037 mentions this issue: all: add Unwrap and Is methods to various error types

@colega

This comment has been minimized.

Copy link

colega commented Apr 10, 2019

As(nil, ...) panics right now, and IMO it shouldn't. By definition, As(nil, ...) should return false since it doesn't have more errors to unwrap.

I couldn't see this mentioned in this thread, maybe it's tracked somewhere else?

Example: https://play.golang.org/p/4GcPS0fbDug

Edit: I see there's already a merge request for that change: https://go-review.googlesource.com/c/go/+/168598

Thanks

@mfridman

This comment has been minimized.

Copy link

mfridman commented Apr 16, 2019

This is more of a user report after trying to port a few internal pkgs from pkg/errors to golang.org/x/xerrors.

I suggest a middle ground where both %w is available through xerrors.Errorf and an additional function from the std lib that takes an error as the first argument: explicitly preserving + annotating the error with the signature:

(err error, format string, args ...interface{}) error

The explicitness of a Wrapf-like func (call it whatever you want), will prevent ambiguity as to whether an error is wrapped or not, while still having the option to prevent wrapping with Opaque or Errorf through %v/%s.

From a readability perspective, it's immediately obvious an error is being wrapped and annotated vs. spotting the %w verb, which reads close to %v.

Stylistically consistent. It helps if the code looks similar throughout the code base.

IMO formatting verbs should not obfuscate the act of preserving an error type.

A bit more background can be found in this ticket (sorry, didn't realize there was an entire discussion): #31432 (comment)

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Apr 16, 2019

@mfridman people -- including members of the Go team -- keep asking for that (and complaining about %w). So far, we've had no luck :-(

Note there are 3 missing fmt APIs:

func Error(a ...interface{}) error                     
func Errorw(e error, a ...interface{}) error           
func Errorwf(e error, f string, a ...interface{}) error

@changkun changkun referenced this issue Apr 17, 2019

Open

更新进度:1.12 #1

39 of 61 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.