单值上下文中的多个值

问题描述 投票:0回答:6

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

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

go error-handling return-value multiple-return-values
6个回答
96
投票

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

如果其中一个是

error
,它的存在是有原因的(这是函数might失败)并且你应该not绕过它因为如果你这样做,你的后续代码might也会失败悲惨地(例如导致运行时恐慌)。

然而,在某些情况下,您知道代码在任何情况下都不会失败。在这些情况下,您可以提供一个 helper 函数(或方法),它将丢弃

error
(或者如果它仍然发生则引发运行时恐慌)。
如果您从代码中为函数提供输入值,并且您知道它们有效,就会出现这种情况。
template
regexp
包就是很好的例子:如果您在编译时提供有效的模板或正则表达式,您可以确保它们始终可以在运行时无错误地解析。出于这个原因,
template
包提供了
Must(t *Template, err error) *Template
功能,而
regexp
包提供了
MustCompile(str string) *Regexp
功能:它们不返回
error
s 因为它们的预期用途是保证输入的地方有效。

例子:

// "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()
函数,它不会返回
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
中可用,如
gog.Must()
(披露:我是作者)。

替代/简化

如果将

Get()
调用合并到您的辅助函数中,您甚至可以进一步简化它,我们称它为
MustGet
:

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

用法:

val := MustGet(1).Value

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

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

在 Golang 的正常功能上像“ok”一样返回映射


9
投票

是的,有。

令人惊讶吧?您可以使用简单的

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-

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

您可以从这篇文章阅读更多关于其背后的科学知识。感谢作者。


7
投票

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

您可以使用一些技术来延迟错误处理,请参阅 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
}

在博客文章的这个例子中,他说明了如何创建一个

errWriter
类型,该类型将错误处理推迟到您完成调用
write
.


5
投票

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

我想一个破解方法是返回一个值数组而不是“item”和“err”,然后就这样做

item, _ := Get(1)[0]
但我不推荐这个。


3
投票

这样怎么样?

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]
    }
}

1
投票

这是一个带有假设检查的通用辅助函数:

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(parameters_of_g)) 将在绑定返回后调用 f g 的值按顺序传递给 f 的参数。


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

© www.soinside.com 2019 - 2024. All rights reserved.