must
must 用于简化一段连续逻辑中的 Go 错误处理。OK、Get、Get2 和 Get3 会在收到非 nil error 时触发带有内部标记的 panic;Do、Call、Call2 和 Call3 在统一边界捕获这种 panic,并返回原始 error。
环境要求
本库使用了 Go 1.18 引入的泛型,因此不支持更早的 Go 版本。
安装
go get github.com/viocha/must@v0.1.0
import "github.com/viocha/must"
为什么使用 must
假设一段逻辑需要依次解析端口、解析超时时间并校验配置,常规写法需要在每一步检查 error:
func loadSettings(rawPort, rawTimeout string) (int, time.Duration, error) {
port, err := strconv.Atoi(rawPort)
if err != nil {
return 0, 0, err
}
timeout, err := time.ParseDuration(rawTimeout)
if err != nil {
return 0, 0, err
}
if err := validateSettings(port, timeout); err != nil {
return 0, 0, err
}
return port, timeout, nil
}
使用 must 后,每个调用只保留业务返回值,错误在 Call2 边界统一返回:
func loadSettings(rawPort, rawTimeout string) (int, time.Duration, error) {
return must.Call2(func() (int, time.Duration) {
port := must.Get(strconv.Atoi(rawPort))
timeout := must.Get(time.ParseDuration(rawTimeout))
must.OK(validateSettings(port, timeout))
return port, timeout
})
}
闭包中连续调用了两次 Get 和一次 OK。任何一步失败都会立即终止后续逻辑,并由 Call2 返回对应的原始 error。
API
func OK(err error)
func Get[T any](v T, err error) T
func Get2[T1, T2 any](v1 T1, v2 T2, err error) (T1, T2)
func Get3[T1, T2, T3 any](v1 T1, v2 T2, v3 T3, err error) (T1, T2, T3)
func Do(fn func()) error
func Call[T any](fn func() T) (T, error)
func Call2[T1, T2 any](fn func() (T1, T2)) (T1, T2, error)
func Call3[T1, T2, T3 any](fn func() (T1, T2, T3)) (T1, T2, T3, error)
API 按业务返回值数量一一对应:
| 业务返回值数量 |
将 error 转为 panic |
将 panic 转回 error |
| 0 |
OK |
Do |
| 1 |
Get |
Call |
| 2 |
Get2 |
Call2 |
| 3 |
Get3 |
Call3 |
使用 Do
Do 用于没有业务返回值、只需要依次完成多个操作的场景:
err := must.Do(func() {
must.OK(os.MkdirAll(directory, 0o755))
must.OK(os.WriteFile(filename, data, 0o644))
})
if err != nil {
return err
}
如果创建目录失败,不会继续写入文件;如果写入文件失败,Do 会返回 os.WriteFile 产生的原始 error。
使用 Call
Call 用于返回一个业务值:
config, err := must.Call(func() Config {
data := must.Get(os.ReadFile(filename))
var config Config
must.OK(json.Unmarshal(data, &config))
must.OK(config.Validate())
return config
})
读取、解析或校验任意一步失败时,config 为 Config 的零值,err 为失败步骤产生的原始 error。
使用 Call2 和 Call3
Call2 用于返回两个业务值:
user, token, err := must.Call2(func() (User, string) {
user := must.Get(loadUser(userID))
token := must.Get(createToken(user))
must.OK(saveLoginRecord(user, token))
return user, token
})
Call3 用于返回三个业务值:
host, port, secure, err := must.Call3(func() (string, int, bool) {
data := must.Get(os.ReadFile(filename))
config := must.Get(parseServerConfig(data))
must.OK(config.Validate())
return config.Host, config.Port, config.Secure
})
发生错误时,Call2 或 Call3 会将所有业务返回值置为对应类型的零值,并返回原始 error。
panic 处理范围
Do、Call、Call2 和 Call3 只捕获由本包 OK、Get、Get2 或 Get3 触发的 panic。业务代码主动触发的 panic 和 Go runtime panic 不会被吞掉,而是保持原值继续向上传播。
err := must.Do(func() {
value := must.Get(loadValue())
must.OK(saveValue(value))
panic("unexpected")
})
上面的 unexpected 不会被转换为 error,而会继续向上传播。
panic 只能在同一个 goroutine 中被 recover。不要期望外层的 Do 或 Call 捕获子 goroutine 中触发的 panic。
如果 OK 或 Get 系列触发的 panic 没有被 Do 或 Call 系列捕获,输出会同时包含 must 标记和原始错误消息:
panic: must: 原始错误消息