英文:
fmt.Println() stops printing chain at wrapped custom error (golang)
问题
我想知道为什么在Go/golang中,当使用fmt.Errorf()创建的所有包装错误都打印出来时,fmt.Println()会打印出某个错误链中的所有错误,但当其中一个错误是自定义错误(也进行了包装)时,fmt.Println()会停止解包和打印错误链,在自定义错误位置停止。
这是一些简化的示例代码:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func main() {
level1Err := errors.New("[Error in L1]: Boom")
level2Err := fmt.Errorf("[Error in L2]: Wrap L1Err %w", level1Err)
level3Err := fmt.Errorf("[Error in L3]: Wrap L2Err %w", level2Err)
//level3Err := &CustomError{"[Error in L3]: Wrap L2Err", level2Err}
level4Err := fmt.Errorf("[Error in L4]: Wrap L3Err %w", level3Err)
fmt.Println(level4Err)
}
// 当取消注释第28行并注释第29行时的控制台输出:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom
// 当取消注释第29行并注释第28行时的控制台输出:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err
使用Go 1.18版本。
你也可以在https://github.com/MBODM/golang-error-chains-problem找到示例代码。
非常感谢你的帮助。
英文:
i want to know why fmt.Println() in Go/golang does print all the errors of some error-chain, when all wrapped errors in that error-chain where created with fmt.Errorf(). But when one of these errors is a custom error (that also wraps), fmt.Println() stops unwrapping and printing of error chain, at the custom error position.
Here is some simplified sample code:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func main() {
level1Err := errors.New("[Error in L1]: Boom")
level2Err := fmt.Errorf("[Error in L2]: Wrap L1Err %w", level1Err)
level3Err := fmt.Errorf("[Error in L3]: Wrap L2Err %w", level2Err)
//level3Err := &CustomError{"[Error in L3]: Wrap L2Err", level2Err}
level4Err := fmt.Errorf("[Error in L4]: Wrap L3Err %w", level3Err)
fmt.Println(level4Err)
}
// Console output, when uncomment line28 and comment line29:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err [Error in L2]: Wrap L1Err [Error in L1]: Boom
// Console output, when uncomment line29 and comment line28:
// [Error in L4]: Wrap L3Err [Error in L3]: Wrap L2Err
Using Go 1.18
You can also found the sample code at https://github.com/MBODM/golang-error-chains-problem
Any help is rather appreciated
答案1
得分: 1
如果你查看现有的一个示例来自go源代码:
// SyscallError记录来自特定系统调用的错误。
type SyscallError struct {
Syscall string
Err error
}
func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
func (e *SyscallError) Unwrap() error { return e.Err }
那么你的CustomError#Error()方法应该是:
func (e *CustomError) Error() string {
if e.Err == nil {
return e.Msg
}
return e.Msg + ": " + e.Err.Error()
}
请参考这个playground示例:
[L4中的错误]:包装L3Err [L3中的ErrorC]:包装L2Err:[L2中的错误]:包装L1Err [L1中的错误]:Boom
英文:
If you look at an existing example from go source code:
// SyscallError records an error from a specific system call.
type SyscallError struct {
Syscall string
Err error
}
func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
func (e *SyscallError) Unwrap() error { return e.Err }
So your CustomError#Error() method should be:
func (e *CustomError) Error() string {
if e.Err == nil {
return e.Msg
}
return e.Msg + ": " + e.Err.Error()
}
See this playground example:
[Error in L4]: Wrap L3Err [ErrorC in L3]: Wrap L2Err: [Error in L2]: Wrap L1Err [Error in L1]: Boom
答案2
得分: 1
为了帮助像我这样的人(作为一个Go的新手,我在这方面遇到了很多困难),我在这里回答了自己的问题:
似乎fmt.Println(topLevelErr)并没有解包所有的错误并打印所有的错误(通过调用每个解包错误的.Error()方法并用一些“:”作为错误之间的分隔符打印所有的错误),这与我最初的想法不同。事实并非如此。
相反,所有的Go库错误都是通过自己实现error接口来完成的:它们的Error()方法(error的实现)会检查它们自己内部是否有一些包装错误,如果有,就会打印出来。这意味着它们会打印自己的错误消息+“:”+包装错误的消息。
你可以通过查看Go源代码来验证这一点,就像下面的VonC所提到的那样。
我个人认为这是一个相当愚蠢的设计。而且完全没有必要,因为所有的错误都已经被包装了。因此,所有的错误都可以通过go-lib中的errors.Unwrap()方法手动解包。解包后,我们(或者也可以是fmt.Println())可以打印出每个错误的错误消息,以及一些分隔的“:”内容。这将是一个比“每个错误都在自己的消息中包装了它内部错误的消息”更好的设计。我认为这就是为什么它让我感到困惑的原因。:)
<s>
另外,我建议你在下面的示例代码中做和我一样的事情:至少让fmt.Errorf()创建那个包装的错误消息,而不是自己放入一些“:”字符串。这样,当包装错误的行为发生变化时,你的代码也会跟着变化。
</s>
之前的建议是错误的,所以我把它删除了。我不小心认为fmt.Errorf()在用%w包装错误时会在文本中添加一个“:”。这是不正确的。正如VonC已经指出的:Go标准库中的大部分内容在实现它们的错误接口时都是这样做的:
> return "own error text" + ": " + e.err.Error()
所以,无论你在错误的实现中使用哪个,都没有关系:
return fmt.Errorf("%s: %w", "own error text", e.err).Error()return "own error text" + ": " + e.err.Error()
在这两种情况下,你都需要自己做“:”的事情。简而言之:fmt.Errorf()函数不会自动将“:”放入创建的错误消息中。
这就是全部内容的展示:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
if e.Err != nil {
// 在这里添加了一个“:”,因为fmt.Errorf()并没有自动做到这一点(就像我之前错误地认为的那样)!
wrappedError := fmt.Errorf("%s: %w", e.Msg, e.Err)
wrappedErrorMsg := wrappedError.Error()
return wrappedErrorMsg
// 你也可以像大多数go-lib包一样这样做:
// return e.Msg + ": " + e.Err.Error()
}
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func printAllWrappedErrors(topLevelError error) {
fmt.Println(topLevelError)
}
func printCustomErrorOnly(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e.Msg) // <-- 这是不同之处
}
}
func printCustomErrorIncludingAllWrappedErrors(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e) // <-- 这是不同之处
}
}
为了更好地理解,可以尝试使用这个示例代码。你可以在GitHub上找到它这里。
非常感谢VonC的及时帮助和启动!非常有帮助!:)
玩得开心。
英文:
To help other ppls like me (i had a hard time with this, as a Go newbie), i answer my own question here:
It seems fmt.Println(topLevelErr) NOT unwraps all the errors and prints all that errors (by calling .Error() of every unwrapped error) with some ": " as delimiter between the errors, as i thought initially. This is NOT what happens.
Instead all the go-lib errors do this by themselfes, by implementing the error interface in this way: Their Error() method (error implementation) tests if they have some wrapped error inside of themselfes and if so, print it "chained". This means they print their own error msg + ": " + the wrapped error´s msg.
You can see this, if you have a look at the Go source code, as VonC mentioned below.
I personally would call this a rather stupid design. And totally unnecessary too, since all errors are wrapped anyway. Therefore all errors could be unwrapped manually, via errors.Unwrap() method from go-lib. When unwrapped, we (or also fmt.Println()) could print the error msg of every error, together with some delimiting ": " stuff. This would be a way better design, than "every error is wrapping the msg of it´s inner error inside it´s own msg". I think that´s the reason why it confused me a lot. ![]()
<s>
Also i advice you to do the same thing as i did in sample code below: At least let fmt.Errorf() create that wrapped error msg, instead of putting some ": " string into it, by yourself. So it is granted, when the wrapped error behaviour changes, yours do also.
</s>
Previous advice was incorrect, so i removed it. I accidentally thought fmt.Errorf() adds a ":" to the text, when wrapping the error with %w. This is not true. As VonC already pointed out: Most stuff in the Go standard library do this in the implementation of their error interface:
> return "own error text" + ": " + e.err.Error()
So it doesnt matter, which of these you use, in your error implementation:
return fmt.Errorf("%s: %w", "own error text", e.err).Error()return "own error text" + ": " + e.err.Error()
In both cases, you need to do the ": " thingy by your own. In short: The fmt.Errorf() function does NOT automatically put the ": " into the created error´s msg.
This shows, what´s all about:
type CustomError struct {
Msg string
Err error
}
func (e *CustomError) Error() string {
if e.Err != nil {
// Added a ":" here, because fmt.Errorf() does NOT
// automatically do this (as i accidentally thought above)!
wrappedError := fmt.Errorf("%s: %w", e.Msg, e.Err)
wrappedErrorMsg := wrappedError.Error()
return wrappedErrorMsg
// Also you can do this instead (like most go-lib pkgs):
// return e.Msg + ": " + e.Err.Error()
}
return e.Msg
}
func (e *CustomError) Unwrap() error {
return e.Err
}
func printAllWrappedErrors(topLevelError error) {
fmt.Println(topLevelError)
}
func printCustomErrorOnly(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e.Msg) // <-- This is the difference
}
}
func printCustomErrorIncludingAllWrappedErrors(topLevelError error) {
var e *CustomError
if errors.As(topLevelError, &e) {
fmt.Println(e) // <-- This is the difference
}
}
For a better understanding just play around with the sample.
You can find it here on GitHub.
And big THX to VonC for the prompt help and the Kickoff! Was very helpful! ![]()
Have fun.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论