golang返回多个值问题

时间:2015-05-08 17:04:38

标签: function go

我想知道为什么这是有效的代码:

func FindUserInfo(id string) (Info, bool) {
    return all[id]
}

但这不是

{{1}}

有没有办法避免临时变量?

5 个答案:

答案 0 :(得分:14)

您可以使用命名返回来保存几个击键:

func FindUserInfo(id string) (i Info, ok bool) {
    i, ok = all[id]
    return
}

但除此之外,我不认为你想要什么是可能的。

答案 1 :(得分:13)

为了详细说明我的commentEffective Go提到访问地图密钥的多值分配称为"逗号确定"图案。

  

有时您需要区分缺失的条目和零值。是否有" UTC"或者是空字符串,因为它根本不在地图中?您可以通过多种分配形式进行区分。

var seconds int
var ok bool
seconds, ok = timeZone[tz]
  

由于显而易见的原因,这被称为“逗号确定”成语。在这个例子中,如果存在tz,则将适当地设置秒,并且ok将为真;如果没有,秒将被设置为零,ok将为false。

Playground demonstrating this

我们可以看到,这与调用常规函数不同,编译器会告诉您出现错误:

package main

import "fmt"

func multiValueReturn() (int, int) {
    return 0, 0
}

func main() {
    fmt.Println(multiValueReturn)

    asgn1, _ := multiValueReturn()

    asgn2 := multiValueReturn()
}

playground这将输出

# command-line-arguments
/tmp/sandbox592492597/main.go:14: multiple-value multiValueReturn() in single-value context

这给了我们一个提示,它可能是编译器正在做的事情。 Searching the source code for" commaOk"给了我们一些看的地方,包括types.unpack

在撰写本文时,这个方法的godoc读取:

// unpack takes a getter get and a number of operands n. If n == 1, unpack
// calls the incoming getter for the first operand. If that operand is
// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a
// function call, or a comma-ok expression and allowCommaOk is set, the result
// is a new getter and operand count providing access to the function results,
// or comma-ok values, respectively. The third result value reports if it
// is indeed the comma-ok case. In all other cases, the incoming getter and
// operand count are returned unchanged, and the third result value is false.
//
// In other words, if there's exactly one operand that - after type-checking
// by calling get - stands for multiple operands, the resulting getter provides
// access to those operands instead.
//
// If the returned getter is called at most once for a given operand index i
// (including i == 0), that operand is guaranteed to cause only one call of
// the incoming getter with that i.
//

这个关键点是这个方法似乎确定某事物是否实际 a"逗号确定"情况下。

深入研究该方法告诉我们,它将检查操作数的模式是否索引地图,或者模式是否设置为commaok(其中{{3}确实给了我们关于它何时使用的许多提示,但是在commaok搜索来源的分配时,我们可以看到它在is definedgetting a value from a channel时被使用了。记住加粗的位用于以后!

if x0.mode == mapindex || x0.mode == commaok {
    // comma-ok value
    if allowCommaOk {
        a := [2]Type{x0.typ, Typ[UntypedBool]}
        return func(x *operand, i int) {
            x.mode = value
            x.expr = x0.expr
            x.typ = a[i]
        }, 2, true
    }
    x0.mode = value
}

allowCommaOk是函数的参数。检查在该文件中调用unpack的位置,我们可以看到所有调用者都将false作为参数传递。搜索存储库的其余部分会导致我们type assertions中的assignments.go

l := len(lhs)
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())

因为我们似乎只能使用"逗号确定"模式在执行多值赋值时得到两个返回值,这似乎是正确的地方!在上面的代码中,检查左侧的长度,当调用unpack时,allowCommaOk参数是l == 2 && !returnPos.IsValid()的结果。 !returnPos.IsValid()在这里有些令人困惑,因为这意味着Checker.initVars() method位置,但我们会忽略它。

在这种方法中我们得到了进一步的结果:

var x operand
if commaOk {
    var a [2]Type
    for i := range a {
        get(&x, i)
        a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
    }
    check.recordCommaOkTypes(rhs[0], a)
    return
}

那么这一切告诉了我们什么呢?

  • 由于unpack方法在allowCommaOk {{1}中除了之外的所有<{1}}参数都被硬编码为false方法,我们可以假设您在执行赋值时只获得两个值,并且在左侧有两个变量。
  • assignment.go方法将通过检查您是否正在为切片编制索引,从通道中获取值来确定您实际是否获得Checker.initVars()值作为回报,或做一个类型断言
  • 由于在执行作业时只能获得unpack值,因此在您的特定情况下,您将始终需要使用变量

答案 2 :(得分:6)

简单地说:你的第二个例子不是有效的原因Go代码是因为语言规范是这样说的。 ;)

Indexing a map仅在两个变量的赋值中产生二次值。退货声明不是作业。

  

在特殊形式的赋值或初始化中使用的类型map [K] V的地图a上的索引表达式

     

v,ok = a [x]
  v,ok:= a [x]
  var v,ok = a [x]

     

产生一个额外的无类型布尔值。如果键x存在于映射中,则ok的值为true,否则为false。

此外,索引地图不是&#34; single call to a multi-valued function&#34;,这是从函数返回值的三种方法之一(第二种方法,其他两种方式不相关) ):

  

有三种方法可以从具有结果类型的函数返回值:

     
      
  1. 可以在&#34; return&#34;中明确列出一个或多个返回值。声明。每个表达式必须是单值的,并且可以赋值给函数结果类型的相应元素。

  2.   
  3. &#34;中的表达式列表&#34;语句可以是对多值函数的单个调用。效果就好像从该函数返回的每个值都分配给具有相应值类型的临时变量,然后是&#34; return&#34;列出这些变量的声明,此时前一种情况的规则适用。

  4.   
  5. 如果函数的结果类型指定其结果参数的名称,则表达式列表可以为空。结果参数充当普通局部变量,并且函数可以根据需要为它们分配值。 &#34;返回&#34; statement返回这些变量的值。

  6.   

至于你的实际问题:避免临时变量的唯一方法是使用非临时变量,但通常这是非常不明智的 - 即使在安全的情况下也可能没有太多优化。

那么,为什么语言规范不允许这种特殊用途的地图索引(或类型断言或频道接收,两者都可以利用&#34;逗号确定&#34;成语)退货声明?这是一个很好的问题。我的猜测:保持语言规范简单。

答案 3 :(得分:2)

我不是专家,但我相信当您尝试返回数组return all[id]时,您会收到编译时错误。原因可能是因为函数返回类型被特别提到为(Info, bool),当您执行return all[id]时,它无法将all[id]的返回类型映射到(Info, bool)

但是上面提到的解决方案,返回的变量iok与函数(i Info, ok bool)的返回类型中提到的相同,因此编译器知道它是什么&# 39; s返回而不是仅仅做(i Info, ok bool)

答案 4 :(得分:1)

默认情况下,golang中的地图在访问密钥时返回单个值

https://blog.golang.org/go-maps-in-action

因此,return all[id]不会为需要2个返回值的函数进行编译。