单值上下文中的多个值

时间:2015-01-30 00:10:16

标签: go error-handling return-value multiple-return-values

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

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

6 个答案:

答案 0 :(得分:66)

如果是多值返回函数,则在调用函数时无法引用结果特定值的字段或方法。

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

但是,在某些情况下,知道代码在任何情况下都不会失败。在这些情况下,您可以提供帮助功能(或方法),该功能将丢弃error(或者如果仍然发生则引发运行时恐慌)。
如果您从代码中提供函数的输入值,并且您知道它们有效,则可能出现这种情况 这方面的好例子是templateregexp包:如果在编译时提供有效的模板或正则表达式,则可以确保在运行时始终可以无错误地解析它们。出于这个原因,template包提供了Must(t *Template, err error) *Template功能,regexp包提供了MustCompile(str string) *Regexp功能:他们不会返回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()函数,该函数不会返回{ {1}}但如果仍然发生,则引发运行时恐慌:

error

但是你不应该在所有情况下使用它,只要你确定它成功。用法:

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

替代/简化

如果您将val := Must(Get(1)).Value 调用合并到帮助函数中,您甚至可以进一步简化它,让我们称之为Get()

MustGet

用法:

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

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

how to parse multiple returns in golang

Return map like 'ok' in Golang on normal functions

答案 1 :(得分: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之前推迟错误处理。

答案 2 :(得分:5)

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

我认为这样做的一个方法是返回一个值数组而不是" item"并且"错误",然后就这样做     item, _ := Get(1)[0] 但我不推荐这个。

答案 3 :(得分:4)

是的,有。

令人惊讶,是吧?您可以使用简单的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-

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

您可以从this article了解更多有关背后的科学知识。致作者的信用。

答案 4 :(得分: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]
    }
}

答案 5 :(得分:0)

这是带有假设检查的通用帮助程序功能:

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)

使之成为可能的技巧:Multi-values returned from one function call can be passed in as multi-variable arguments to another function.

在特殊情况下,如果一个函数或方法g的返回值数量相等并且可以分别分配给另一个函数或方法f的参数,则调用f(g(parameters_of_g))将在绑定后调用f g的返回值按顺序返回f的参数。


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