Go使用泛型模仿 Rust 实现一个简单的 Result 类型

685 阅读1分钟

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()))