单值上下文中的多个值
Multiple values in single-value context
由于 Go 中的错误处理,我经常以多值函数结束。到目前为止,我的管理方式非常混乱,我正在寻找最佳实践来编写更清晰的代码。
假设我有以下功能:
type Item struct {
Value int
Name string
}
func Get(value int) (Item, error) {
// some code
return item, nil
}
如何优雅地将新变量分配给 item.Value
。在介绍错误处理之前,我的函数刚刚返回 item
,我可以简单地这样做:
val := Get(1).Value
现在我这样做:
item, _ := Get(1)
val := item.Value
没有办法直接访问第一个返回的变量吗?
不,但这是一件好事,因为您应该始终处理您的错误。
您可以使用一些技巧来延迟错误处理,请参阅 Rob Pike 的 Errors are values。
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
return ew.err
}
在这个来自博客 post 的示例中,他说明了如何创建一个 errWriter
类型来延迟错误处理,直到您完成调用 write
.
不,您不能直接访问第一个值。
我想一个破解方法是 return 一组值而不是 "item" 和 "err",然后就这样做
item, _ := Get(1)[0]
但我不推荐这个。
这样怎么样?
package main
import (
"fmt"
"errors"
)
type Item struct {
Value int
Name string
}
var items []Item = []Item{{Value:0, Name:"zero"},
{Value:1, Name:"one"},
{Value:2, Name:"two"}}
func main() {
var err error
v := Get(3, &err).Value
if err != nil {
fmt.Println(err)
return
}
fmt.Println(v)
}
func Get(value int, err *error) Item {
if value > (len(items) - 1) {
*err = errors.New("error")
return Item{}
} else {
return items[value]
}
}
如果是多值 return 函数,则在调用函数时不能引用结果的特定值的字段或方法。
如果其中之一是 error
,它存在的原因是 (即函数 可能 失败) 你应该不绕过它,因为如果你这样做,你的后续代码可能也会失败(例如导致运行时恐慌)。
但是,在某些情况下,您知道代码在任何情况下都不会失败。在这些情况下,您可以提供一个 helper 函数(或方法),它将丢弃 error
(或者如果它仍然发生则引发运行时恐慌)。
如果您从代码中为函数提供输入值,并且您知道它们有效,就会出现这种情况。
template
and regexp
packages: if you provide a valid template or regexp at compile time, you can be sure they can always be parsed without errors at runtime. For this reason the template
package provides the Must(t *Template, err error) *Template
function and the regexp
package provides the MustCompile(str string) *Regexp
函数就是很好的例子:它们没有 return error
,因为它们的预期用途是保证输入有效。
示例:
// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))
// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
回到你的案例
IF 你可以确定 Get()
不会为某些输入值生成 error
,你可以创建一个辅助 Must()
函数,它不会 return error
但如果它仍然发生则引发运行时恐慌:
func Must(i Item, err error) Item {
if err != nil {
panic(err)
}
return i
}
但是你不应该在所有情况下都使用它,只有当你确定它成功时。用法:
val := Must(Get(1)).Value
Go 1.18 泛型更新: Go 1.18 添加泛型支持,现在可以编写泛型 Must()
函数:
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
这在 github.com/icza/gog
, as gog.Must()
中可用(披露:我是作者)。
替代/简化
如果将 Get()
调用合并到您的辅助函数中,您甚至可以进一步简化它,我们称它为 MustGet
:
func MustGet(value int) Item {
i, err := Get(value)
if err != nil {
panic(err)
}
return i
}
用法:
val := MustGet(1).Value
查看一些有趣/相关的问题:
是的,有。
很惊讶吧?您可以使用简单的 mute
函数从多个 return 中获取特定值:
package main
import "fmt"
import "strings"
func µ(a ...interface{}) []interface{} {
return a
}
type A struct {
B string
C func()(string)
}
func main() {
a := A {
B:strings.TrimSpace(µ(E())[1].(string)),
C:µ(G())[0].(func()(string)),
}
fmt.Printf ("%s says %s\n", a.B, a.C())
}
func E() (bool, string) {
return false, "F"
}
func G() (func()(string), bool) {
return func() string { return "Hello" }, true
}
https://play.golang.org/p/IwqmoKwVm-
请注意您如何 select 值数字,就像您从 slice/array 中获取值一样,然后是获取实际值的类型。
您可以从 this article 阅读更多关于其背后的科学知识。感谢作者。
这是一个带有假设检查的通用辅助函数:
func assumeNoError(value interface{}, err error) interface{} {
if err != nil {
panic("error encountered when none assumed:" + err.Error())
}
return value
}
由于此 return 是 interface{}
,您通常需要将其转换回函数的 return 类型。
例如,OP 的示例名为 Get(1)
,其中 returns (Item, error)
。
item := assumeNoError(Get(1)).(Item)
As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order.
这个答案大量借鉴了现有答案,但 none 提供了这种形式的简单通用解决方案。
由于 Go 中的错误处理,我经常以多值函数结束。到目前为止,我的管理方式非常混乱,我正在寻找最佳实践来编写更清晰的代码。
假设我有以下功能:
type Item struct {
Value int
Name string
}
func Get(value int) (Item, error) {
// some code
return item, nil
}
如何优雅地将新变量分配给 item.Value
。在介绍错误处理之前,我的函数刚刚返回 item
,我可以简单地这样做:
val := Get(1).Value
现在我这样做:
item, _ := Get(1)
val := item.Value
没有办法直接访问第一个返回的变量吗?
不,但这是一件好事,因为您应该始终处理您的错误。
您可以使用一些技巧来延迟错误处理,请参阅 Rob Pike 的 Errors are values。
ew := &errWriter{w: fd} ew.write(p0[a:b]) ew.write(p1[c:d]) ew.write(p2[e:f]) // and so on if ew.err != nil { return ew.err }
在这个来自博客 post 的示例中,他说明了如何创建一个 errWriter
类型来延迟错误处理,直到您完成调用 write
.
不,您不能直接访问第一个值。
我想一个破解方法是 return 一组值而不是 "item" 和 "err",然后就这样做
item, _ := Get(1)[0]
但我不推荐这个。
这样怎么样?
package main
import (
"fmt"
"errors"
)
type Item struct {
Value int
Name string
}
var items []Item = []Item{{Value:0, Name:"zero"},
{Value:1, Name:"one"},
{Value:2, Name:"two"}}
func main() {
var err error
v := Get(3, &err).Value
if err != nil {
fmt.Println(err)
return
}
fmt.Println(v)
}
func Get(value int, err *error) Item {
if value > (len(items) - 1) {
*err = errors.New("error")
return Item{}
} else {
return items[value]
}
}
如果是多值 return 函数,则在调用函数时不能引用结果的特定值的字段或方法。
如果其中之一是 error
,它存在的原因是 (即函数 可能 失败) 你应该不绕过它,因为如果你这样做,你的后续代码可能也会失败(例如导致运行时恐慌)。
但是,在某些情况下,您知道代码在任何情况下都不会失败。在这些情况下,您可以提供一个 helper 函数(或方法),它将丢弃 error
(或者如果它仍然发生则引发运行时恐慌)。
如果您从代码中为函数提供输入值,并且您知道它们有效,就会出现这种情况。
template
and regexp
packages: if you provide a valid template or regexp at compile time, you can be sure they can always be parsed without errors at runtime. For this reason the template
package provides the Must(t *Template, err error) *Template
function and the regexp
package provides the MustCompile(str string) *Regexp
函数就是很好的例子:它们没有 return error
,因为它们的预期用途是保证输入有效。
示例:
// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))
// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
回到你的案例
IF 你可以确定 Get()
不会为某些输入值生成 error
,你可以创建一个辅助 Must()
函数,它不会 return error
但如果它仍然发生则引发运行时恐慌:
func Must(i Item, err error) Item {
if err != nil {
panic(err)
}
return i
}
但是你不应该在所有情况下都使用它,只有当你确定它成功时。用法:
val := Must(Get(1)).Value
Go 1.18 泛型更新: Go 1.18 添加泛型支持,现在可以编写泛型 Must()
函数:
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
这在 github.com/icza/gog
, as gog.Must()
中可用(披露:我是作者)。
替代/简化
如果将 Get()
调用合并到您的辅助函数中,您甚至可以进一步简化它,我们称它为 MustGet
:
func MustGet(value int) Item {
i, err := Get(value)
if err != nil {
panic(err)
}
return i
}
用法:
val := MustGet(1).Value
查看一些有趣/相关的问题:
是的,有。
很惊讶吧?您可以使用简单的 mute
函数从多个 return 中获取特定值:
package main
import "fmt"
import "strings"
func µ(a ...interface{}) []interface{} {
return a
}
type A struct {
B string
C func()(string)
}
func main() {
a := A {
B:strings.TrimSpace(µ(E())[1].(string)),
C:µ(G())[0].(func()(string)),
}
fmt.Printf ("%s says %s\n", a.B, a.C())
}
func E() (bool, string) {
return false, "F"
}
func G() (func()(string), bool) {
return func() string { return "Hello" }, true
}
https://play.golang.org/p/IwqmoKwVm-
请注意您如何 select 值数字,就像您从 slice/array 中获取值一样,然后是获取实际值的类型。
您可以从 this article 阅读更多关于其背后的科学知识。感谢作者。
这是一个带有假设检查的通用辅助函数:
func assumeNoError(value interface{}, err error) interface{} {
if err != nil {
panic("error encountered when none assumed:" + err.Error())
}
return value
}
由于此 return 是 interface{}
,您通常需要将其转换回函数的 return 类型。
例如,OP 的示例名为 Get(1)
,其中 returns (Item, error)
。
item := assumeNoError(Get(1)).(Item)
As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order.
这个答案大量借鉴了现有答案,但 none 提供了这种形式的简单通用解决方案。