编写没有泛型的通用错误处理函数
Writing generic error handling function without generics
我知道 Go 将来不会有泛型,并且有一些建议用其他构造替换它们。但是在我下面的例子中,我被卡住了。
func P(any interface{}, err error) (interface{}) {
if err != nil {
panic("error: "+ err.Error())
}
return any
}
正如您可能猜到的那样,我试图在出现任何错误时都失败,并希望将 P()
放在任何返回两个结果且第二个是错误的函数周围。这工作正常,但是 any
正在丢失它的类型信息并且在结果中只是一个空接口。
因为我也在调用 lib 函数,所以我看不到使用接口或反射来解决这个问题的方法。
有什么想法吗?我是完全走错了路还是接近目标了?
一个解决方案是 go generate
您的 P()
函数,一个用于您需要使用的每种具体类型。
请参阅以下示例:
- "Generic programming in Go using "
go generate
"".
- "joeshaw/gengen"
- "cheekybits/genny"
- "clipperhouse/gen"
- "Achieving type generic functions in Go, without using reflections"
这将使调用那些 lib 函数更容易,因为生成的具体 P() 实现将使用正确的类型而不是 interface{}。
你想做什么会需要泛型,但正如你已经提到的,Go 不支持泛型类型。因此,您不能创建不会丢失类型的通用函数。
您必须为要支持的每种类型创建这样的函数。请注意,标准库已经包含其中一些名称为 MustXXX()
的内容,您可以开箱即用,例如:
template.Must(t *Template, err error) *Template
或 "similar" 抑制 error
的函数,但如果仍然出现,则出现恐慌,例如:
regexp.MustCompile(str string) *Regexp
(抑制 error
但如果 str
不是有效的正则表达式则出现恐慌)
如果您计划只是对错误 (坏主意) 或记录它们感到恐慌,那么只需定义一个函数来执行此操作并使用它。例如
func checkErr(err error) {
if err != nil {
log.Println(err)
}
}
// ...
func foo() {
a, err := doA()
checkErr(err)
b, err := doB()
checkErr(err)
// etc.
}
用户 twotwotwo 已经链接到 Errors are values 文章,该文章显示了有关如何减少错误处理重复性的更多示例。但我建议只写整个 if err != nil
东西,因为根据我的经验,每三次错误,如果不是第二次,都需要一些额外的处理。
随着 Go 1.18(2022 年初),类型参数将被引入该语言。
根据当前的 accepted proposal 规范,您将能够编写通用 Must
函数而不牺牲类型安全性。
它将如下所示:
package main
import (
"fmt"
"errors"
)
func Must[T any](v T, err error) T {
if err != nil {
panic("error: " + err.Error())
}
return v
}
func main() {
fmt.Println(Must(test1())) // 450
fmt.Println(Must(test2())) // panics...
}
func test1() (int, error) {
return 450, nil
}
func test2() (string, error) {
return "", errors.New("problem")
}
我知道 Go 将来不会有泛型,并且有一些建议用其他构造替换它们。但是在我下面的例子中,我被卡住了。
func P(any interface{}, err error) (interface{}) {
if err != nil {
panic("error: "+ err.Error())
}
return any
}
正如您可能猜到的那样,我试图在出现任何错误时都失败,并希望将 P()
放在任何返回两个结果且第二个是错误的函数周围。这工作正常,但是 any
正在丢失它的类型信息并且在结果中只是一个空接口。
因为我也在调用 lib 函数,所以我看不到使用接口或反射来解决这个问题的方法。
有什么想法吗?我是完全走错了路还是接近目标了?
一个解决方案是 go generate
您的 P()
函数,一个用于您需要使用的每种具体类型。
请参阅以下示例:
- "Generic programming in Go using "
go generate
"". - "joeshaw/gengen"
- "cheekybits/genny"
- "clipperhouse/gen"
- "Achieving type generic functions in Go, without using reflections"
这将使调用那些 lib 函数更容易,因为生成的具体 P() 实现将使用正确的类型而不是 interface{}。
你想做什么会需要泛型,但正如你已经提到的,Go 不支持泛型类型。因此,您不能创建不会丢失类型的通用函数。
您必须为要支持的每种类型创建这样的函数。请注意,标准库已经包含其中一些名称为 MustXXX()
的内容,您可以开箱即用,例如:
template.Must(t *Template, err error) *Template
或 "similar" 抑制 error
的函数,但如果仍然出现,则出现恐慌,例如:
regexp.MustCompile(str string) *Regexp
(抑制 error
但如果 str
不是有效的正则表达式则出现恐慌)
如果您计划只是对错误 (坏主意) 或记录它们感到恐慌,那么只需定义一个函数来执行此操作并使用它。例如
func checkErr(err error) {
if err != nil {
log.Println(err)
}
}
// ...
func foo() {
a, err := doA()
checkErr(err)
b, err := doB()
checkErr(err)
// etc.
}
用户 twotwotwo 已经链接到 Errors are values 文章,该文章显示了有关如何减少错误处理重复性的更多示例。但我建议只写整个 if err != nil
东西,因为根据我的经验,每三次错误,如果不是第二次,都需要一些额外的处理。
随着 Go 1.18(2022 年初),类型参数将被引入该语言。
根据当前的 accepted proposal 规范,您将能够编写通用 Must
函数而不牺牲类型安全性。
它将如下所示:
package main
import (
"fmt"
"errors"
)
func Must[T any](v T, err error) T {
if err != nil {
panic("error: " + err.Error())
}
return v
}
func main() {
fmt.Println(Must(test1())) // 450
fmt.Println(Must(test2())) // panics...
}
func test1() (int, error) {
return 450, nil
}
func test2() (string, error) {
return "", errors.New("problem")
}