单值上下文中的多个值

由于 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

有没有直接访问第一个返回变量的方法?

119454 次浏览

不,但这是件好事,因为你应该总是处理你的错误。

您可以使用一些技术来延迟错误处理,请参阅 Rob Pike 的 错误就是价值

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
}

在这个例子中,他说明了如何创建一个 errWriter类型,将错误处理推迟到调用完 write之后。

不,您不能直接访问第一个值。

我认为这个问题的解决方法是返回一个值数组,而不是“ 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]
}
}

在多值返回函数的情况下,当调用该函数时,不能引用结果的特定值的字段或方法。

如果其中一个是 error,那么它就是 原因(即函数 也许吧的失败) ,你应该绕过它,因为如果你这样做,你的后续代码 也许吧也会失败(例如导致运行时恐慌)。

但是,可能会有这样的情况,即您的 知道代码在任何情况下都不会失败。在这些情况下,您可以提供一个 帮手函数(或方法) ,它将丢弃 error(如果仍然发生,则引发运行时恐慌)。
如果您从代码中为函数提供输入值,并且您知道它们可以工作,那么就可以是这种情况。
templateregexp包就是一个很好的例子: 如果您在编译时提供了一个有效的模板或 regexp,那么您就可以确保它们在运行时总是可以被解析而不会出现错误。由于这个原因,template包提供了 Must(t *Template, err error) *Template函数,而 regexp包提供了 regexp0函数: 它们不返回 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]+\]$`)

回到你的案子上

如果 你可以确定 Get()不会为某些输入值产生 error,你可以创建一个辅助 Must()函数,它不会返回 error,但是如果它仍然发生,会引起运行时恐慌:

func Must(i Item, err error) Item {
if err != nil {
panic(err)
}
return i
}

但是你不应该在所有情况下都使用它,只有当你确信它成功的时候。用法:

val := Must(Get(1)).Value

Go 1.18增加了对泛型的支持,现在可以编写一个通用的 Must()函数了:

func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}

这在 github.com/icza/gog中可用,作为 gog.Must()(披露: 我是作者)。

另类/简化

如果您将 Get()调用合并到 helper 函数中,您甚至可以进一步简化它,我们称之为 MustGet:

func MustGet(value int) Item {
i, err := Get(value)
if err != nil {
panic(err)
}
return i
}

用法:

val := MustGet(1).Value

查看一些有趣/相关的问题:

如何向可变参数函数传递多个返回值?

返回地图像在 Golang 的正常功能

有的。

令人惊讶吧? 你可以使用一个简单的 mute函数从多个返回值中得到一个特定的值:

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-

注意如何像从一个切片/数组中选择值编号,然后选择获取实际值的类型。

你可以从 这篇文章.Credits 上读到更多关于这背后的科学知识。

下面是一个带假设检查的通用 helper 函数:

func assumeNoError(value interface{}, err error) interface{} {
if err != nil {
panic("error encountered when none assumed:" + err.Error())
}
return value
}

由于它返回为 interface{},所以通常需要将其强制转换回函数的返回类型。

例如,OP 的例子 Get(1),返回 (Item, error)

item := assumeNoError(Get(1)).(Item)

使之成为可能的技巧: 从一个函数调用返回的多个值可以作为多变量参数传递给另一个函数。

作为一个特例,如果一个函数或方法 g 的返回值数量相等,并且可以单独赋值给另一个函数或方法 f 的参数,那么调用 f (g (reference _ of _ g))将在将 g 的返回值按顺序绑定到 f 的参数之后调用 f。


这个答案大量借鉴了现有的答案,但是没有一个答案提供了这种形式的简单、通用的解决方案。