Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

golang return multiple values issue

Tags:

function

go

I was wondering why this is valid go code:

func FindUserInfo(id string) (Info, bool) {
    it, present := all[id]
    return it, present
}

but this isn't

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

is there a way to avoid the temporary variables?

like image 602
Pablo Fernandez Avatar asked May 08 '15 17:05

Pablo Fernandez


People also ask

How many values can a Go function return?

In Go language, you are allowed to return multiple values from a function, using the return statement. Or in other words, in function, a single return statement can return multiple values.

How do you return a value in Golang?

Golang introduces a concept of “Naked Return” allowing the use of return keyword without explicitly stating the return values in the function body provided that the return values are declared in the function header. However, the variable name must be the same as the one defined in the function header.

How do I return an array in Golang?

In the main() function, we created two arrays of integers intArr, retArr and then we read elements from the user. After that, we passed the created array to the GetArray() function, The GetArray() function modifies the value of the passed array and then returns the modified array to the calling function.


1 Answers

To elaborate on my comment, the Effective Go mentions that the multi-value assignment from accessing a map key is called the "comma ok" pattern.

Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that the empty string because it's not in the map at all? You can discriminate with a form of multiple assignment.

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

For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false.

Playground demonstrating this

We can see that this differs from calling a regular function where the compiler would tell you that something is wrong:

package main

import "fmt"

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

func main() {
    fmt.Println(multiValueReturn)

    asgn1, _ := multiValueReturn()

    asgn2 := multiValueReturn()
}

On the playground this will output

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

This gives us a hint that it may be something the compiler is doing. Searching the source code for "commaOk" gives us a few places to look, including types.unpack

At the time of writing this it this the method's godoc reads:

// 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.
//

The key bits of this being that this method appears to determine whether or not something is actually a "comma ok" case.

Digging into that method tells us that it will check to see if the mode of the operands is indexing a map or if the mode is set to commaok (where this is defined does give us many hints on when it's used, but searching the source for assignments to commaok we can see it's used when getting a value from a channel and type assertions). Remember the bolded bit for later!

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 is a parameter to the function. Checking out where unpack is called in that file we can see that all callers pass false as an argument. Searching the rest of the repository leads us to assignments.go in the Checker.initVars() method.

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

Since it seems that we can only use the "comma ok" pattern to get two return values when doing a multi-value assignment this seems like the right place to look! In the above code the length of the left hand side is checked, and when unpack is called the allowCommaOk parameter is the result of l == 2 && !returnPos.IsValid(). The !returnPos.IsValid() is somewhat confusing here as that would mean that the position has no file or line information associated with it, but we'll just ignore that.

Further down in that method we've got:

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
}

So what does all of this tell us?

  • Since the unpack method takes an allowCommaOk parameter that's hardcoded to false everywhere except in assignment.go's Checker.initVars() method, we can probably assume that you will only ever get two values when doing an assignment and have two variables on the left-hand side.
  • The unpack method will determine whether or not you actually do get an ok value in return by checking if you are indexing a slice, grabbing a value from a channel, or doing a type assertion
  • Since you can only get the ok value when doing an assignment it looks like in your specific case you will always need to use variables
like image 177
Lander Avatar answered Oct 13 '22 01:10

Lander