Go 中的错误处理一直是争议最多的,Rust 通过引入 Result 类型来解决此问题。
随着 Go 1.18 中泛型的引入,我们可以模仿 Rust 实现一个简单的 Result 类型。
package result
type Result[T any] struct {
value T
err error
}
func (r Result[T]) Ok() bool {
return r.err == nil
}
func (r Result[T]) Err() error {
return r.err
}
func (r Result[T]) Unwrap() T {
if r.err != nil {
panic(r.err)
}
return r.value
}
func (r Result[T]) Expect(err error) T {
if r.err != nil {
panic(err)
}
return r.value
}
func New[T any](v T, err error) Result[T] {
return Result[T]{
value: v,
err: err,
}
}
func Match[T any](r Result[T], okF func(T), errF func(error)) {
if r.err != nil {
errF(r.err)
} else {
okF(r.value)
}
}
下面是一个完整的示例,打开或创建 test.txt 文件,并尝试追加写入 “Hello, world”。
package main
import (
"fmt"
"os"
"test/result"
)
func openFile(name string) result.Result[*os.File] {
file, err := os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
return result.New[*os.File](file, err)
}
func main() {
// res.Ok() 等于 true 就可以安全使用 Unwrap了
if res := openFile("test.txt"); res.Ok() {
f := res.Unwrap()
f.WriteString("hello world\n")
f.Close()
} else {
fmt.Println(res.Err())
}
// 由于go没有rust中的match表达式,所以简单包装了一个辅助函数。
result.Match(openFile("test.txt"), func(f *os.File) {
f.WriteString("hello world\n")
f.Close()
}, func(err error) {
fmt.Println(err)
})
f := openFile("test.txt").Unwrap()
f.WriteString("hello world\n")
f.Close()
res := openFile("test.txt")
f = res.Expect(fmt.Errorf("open file fail: %w", res.Err()))
f.WriteString("hello world\n")
f.Close()
}
如果执行成功,可以看到在工程根目录下,多出了 test.txt 文件。
unwrap 和 expect
Result 的处理有时太过于繁琐,和Rust一样 提供了一种简洁的处理方式 unwrap 。即,如果成功,直接返回 Result[T] 中的T值,如果失败,则直接调用 panic,程序结束。
f := openFile("test.txt").Unwrap()
expect 是更人性化的处理方式,允许在调用panic时,返回自定义的错误提示信息,对调试很有帮助。
res := openFile("test.txt")
f = res.Expect(fmt.Errorf("open file fail: %w", res.Err()))