英文:
Return nil or custom error in Go
问题
在Go语言中使用自定义的Error
类型(带有额外字段来捕获一些细节)时,当尝试将nil
作为该类型的值返回时,会出现编译错误,例如cannot convert nil to type DetailedError
或cannot use nil as type DetailedError in return argument
,代码大致如下:
type DetailedError struct {
x, y int
}
func (e DetailedError) Error() string {
return fmt.Sprintf("Error occurred at (%s,%s)", e.x, e.y)
}
func foo(answer, x, y int) (int, DetailedError) {
if answer == 42 {
return 100, nil //!! cannot use nil as type DetailedError in return argument
}
return 0, DetailedError{x: x, y: y}
}
(完整代码段:https://play.golang.org/p/4i6bmAIbRg)
解决这个问题的惯用方式是什么?(或者任何可行的方式...)
实际上,我需要在错误上有额外的字段,因为我通过复杂的逻辑从简单的错误构建详细的错误消息等等。如果我只是回退到"字符串错误",我基本上需要将这些字符串解析成片段,并根据它们进行逻辑处理等等,这似乎非常丑陋(我的意思是,为什么要将信息序列化为字符串,而后面你知道你需要反序列化...)
英文:
When using a custom Error
type in Go (with extra fields to capture some details), when trying to return nil
as value of this type, I get compile errors like cannot convert nil to type DetailedError
or like cannot use nil as type DetailedError in return argument
, from code looking mostly like this:
type DetailedError struct {
x, y int
}
func (e DetailedError) Error() string {
return fmt.Sprintf("Error occured at (%s,%s)", e.x, e.y)
}
func foo(answer, x, y int) (int, DetailedError) {
if answer == 42 {
return 100, nil //!! cannot use nil as type DetailedError in return argument
}
return 0, DetailedError{x: x, y: y}
}
(full snippet: https://play.golang.org/p/4i6bmAIbRg)
What would be the idiomatic way to solve this problem? (Or any way that works...)
I actually need the extra fields on errors because I have detailed error messages constructed by complex logic from simpler ones etc., and if I would just fall back to "string errors" I'd basically have to parse those strings to pieces and have logic happen based on them and so on, which seems really ugly (I mean, why serialize to strings info you know you're gonna need to deserialize later...)
答案1
得分: 22
不要将DetailedError
用作返回类型,始终使用error
:
func foo(answer, x, y int) (int, error) {
if answer == 42 {
return 100, nil //!! 无法将nil用作返回参数中的DetailedError类型
}
return 0, DetailedError{x: x, y: y}
}
你的DetailedError
类型满足error
接口的要求就足够了。然后,在调用者中,如果你关心额外的字段,可以使用类型断言:
value, err := foo(...)
if err != nil {
if detailedErr, ok := err.(DetailedError); ok {
// 使用详细的错误值做一些处理
} else {
// 这是其他类型的错误,根据情况进行处理
}
}
不返回DetailedError
的原因:
现在可能看起来并不重要,但将来你的代码可能会扩展以包含其他错误检查:
func foo(answer, x, y int) (int, error) {
cache, err := fetchFromCache(answer, x, y)
if err != nil {
return 0, fmt.Errorf("无法读取缓存:%s", err)
}
// ...
}
额外的错误类型将不是DetailedError
类型,因此你必须返回error
。
此外,你的方法可能会被其他不知道或不关心DetailedError
类型的调用者使用:
func fooWrapper(answer, x, y int) (int, error) {
// 在调用foo之前做一些处理
result, err := foo(answer, x, y)
if err != nil {
return 0, err
}
// 在调用foo之后做一些处理
return result, nil
}
期望每个函数的调用者都理解自定义错误类型是不合理的,这正是为什么在Go中存在接口,特别是error
接口的原因。
利用它,不要规避它。
即使你的代码永远不会改变,为每个函数或用例创建一个新的自定义错误类型是不可持续的,会使你的代码难以阅读和无法推理。
英文:
Don't use DetailedError
as a return type, always use error
:
func foo(answer, x, y int) (int, error) {
if answer == 42 {
return 100, nil //!! cannot use nil as type DetailedError in return argument
}
return 0, DetailedError{x: x, y: y}
}
The fact that your DetailedError
type satisfies the error
interface is sufficient to make this work. Then, in your caller, if you care about the extra fields, use a type assertion:
value, err := foo(...)
if err != nil {
if detailedErr, ok := err.(DetailedError); ok {
// Do something with the detailed error values
} else {
// It's some other error type, behave accordingly
}
}
Reasons for not returning DetailedError
:
It may not appear to matter right now, but in the future your code may expand to include additional error checks:
func foo(answer, x, y int) (int, error) {
cache, err := fetchFromCache(answer, x, y)
if err != nil {
return 0, fmt.Errorf("Failed to read cache: %s", err)
}
// ...
}
The additional error types won't be of DetailedError
type, so you then must return error
.
Further, your method may be used by other callers that don't know or care about the DetailedError
type:
func fooWrapper(answer, x, y int) (int, error) {
// Do something before calling foo
result, err := foo(answer, x, y)
if err != nil {
return 0, err
}
// Do something after calling foo
return result, nil
}
It's unreasonable to expect every caller of a function to understand a custom error type--this is precisely why interfaces, and in particular the error
interface, exists in Go.
Take advantage of this, don't circumvent it.
Even if your code never changes, having a new custom error type for every function or use-case is unsustainable, and makes your code unreadable and impossible to reason about.
答案2
得分: 2
DetailedError
结构体的零值不是nil
,而是DetailedError{}
。你可以选择返回error
接口而不是DetailedError
。
func foo(answer, x, y int) (int, error) {
或者使用指针
func foo(answer, x, y int) (int, *DetailedError) {
...
//and
func (e *DetailedError) Error() string {
英文:
DetailedError struct
zero value isn't nil
but DetailedError{}
. You can either return error
interface instead of DetailedError
func foo(answer, x, y int) (int, error) {
or use pointer
func foo(answer, x, y int) (int, *DetailedError) {
...
//and
func (e *DetailedError) Error() string {
答案3
得分: 0
解决您的问题的惯用方法是返回错误接口。
如果您实际上需要一个不是错误接口的函数,您应该创建一个扩展错误接口的新接口。
type DetailedErrorInterface interface {
error
GetX() int //需要实现
GetY() int //需要实现
}
然后,您必须更改函数以返回此接口:
func foo(answer, x, y int) (int, DetailedErrorInterface) {
if answer == 42 {
return 100, nil
}
return 0, DetailedError{x: x, y: y}
}
英文:
The idiomatic way to solve your problem is to return the error interface.
If you actually need a function that is not part of the error interface, you should create a new interface that extends the error interface.
type DetailedErrorInterface interface {
error
GetX() int //need to implement
GetY() int //need to implement
}
Then, you must change your function to return this interface:
func foo(answer, x, y int) (int, DetailedErrorInterface) {
if answer == 42 {
return 100, nil
}
return 0, DetailedError{x: x, y: y}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论