EntilZha
EntilZha

Reputation: 1732

Go: Assign multiple return value function to new and old variable

In go there are functions which return two values or more values, commonly one is an error. Suppose that I want to store the first return value into an already initialized variable, but I would like to initialize the variable to contain the error inline. Is there a way to do this?

For example, say I had this code

var a int
//This code doesn't compile because err doesn't exist
a, err = SomeFuncWithTwoReturnValues()
//This code doesn't compile either
a, err := SomeFuncWithTwoReturnValues()

I know you could do this, but I was hoping there was a way to do it all inline

var a int
var err error
a, err = SomeFuncWithTwoReturnValues()

or

a, err := SomeFuncWithTwoReturnValues()

EDIT: The code above actually compiles, so I looked back at my code to drill down more and have created a quick sample that actually replicates the problem (not just in my mind...).

package main

func myfunc() (int, int) {
    return 1, 1
}

func main() {
    a := make([]int, 1)
    a[0], b := myfunc()
    a[0] = b
}

Compiler says main.go|9| non-name a[0] on left side of :=. If I make it = instead of := though then b is never created. I get the feeling that there is not shorthand way to do it though.

Upvotes: 47

Views: 21841

Answers (5)

Silthus
Silthus

Reputation: 1729

As mentioned by the other answers you cannot use assignment and declaration in the same return statement. You have to use either.

However I guess the main reason for your question is cleaning up the code so you don't have to declare an extra err variable above the method or function statement.

You can solve this in two ways:

  1. Declare a global var err error variable and use it in the assignment:
var err error

func MyFunc(someInput string) {
  var a int
  a, err = someOtherFunction()
}
  1. If your method or function returns an error you can use the declared return variable
func MyFunc(someInput string) (err error) {
  var a int
  a, err = someOtherFunction()
  return
}

I mainly have the problem in methods when I want to assign something to a struct member, e.g.:

type MyStruct struct {
  so string
}

func (m *MyStruct) SomeMethod() (err error) {
  m.so, err = SomeFunction()
  // handle error and continue or return it
  return
}

Upvotes: 0

Michael Ben-Nes
Michael Ben-Nes

Reputation: 7645

As mention in the spec, while using:=, if one of the variables is new, then the old one will just be assigned with the new data.

Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // redeclares offset

Upvotes: 0

Zombo
Zombo

Reputation: 1

I ran into this situation like this:

package main
import "os"

func main() {
   var cache struct { dir string }
   // undefined: err
   cache.dir, err = os.UserCacheDir()
   // non-name cache.dir on left side of :=
   cache.dir, err := os.UserCacheDir()
   if err != nil {
      panic(err)
   }
   println(cache.dir)
}

as you discovered, this issue does not have a clean solution. You can declare an extra variable:

dir, err := os.UserCacheDir()
if err != nil {
   panic(err)
}
cache := userCache{dir}

Or, while more verbose, you can declare the error beforehand. This can save memory, as Go does not use a Rust ownership model:

var (
   cache struct { dir string }
   err error
)
cache.dir, err = os.UserCacheDir()

Upvotes: 1

Sebi2020
Sebi2020

Reputation: 2150

Golang is not a very consistent language. This is a good example. At the beginning I was confused and it would be much simpler if they would always allow the := operator. The compiler is smart enough to detect already declared variables:

package main

import "fmt"

func testFunc() (int,error) {
   return 42,fmt.Errorf("Test Error")
}

func main() {
   number1,err := testFunc() // OK
   number2,err := testFunc() // OK, even if err is already defined
   number1,err = testFunc()  // OK
   // number1,err := testFunc() // ERROR: no new variables on left side of :=

   fmt.Println(number1,number2,err)
}

Playground Link: https://play.golang.org/p/eZVB-kG6RtX

It's not consistent, because golang allows you to use := for already declared variables if you assign to them while also introducing a new variable. So the compiler can detect that variables already exists and skip their declaration. But the golang developers decided to allow that only if you introduce at least one new value. The last example shows that.

Upvotes: 7

E. Moffat
E. Moffat

Reputation: 3288

As you've mentioned in the comments, you'll need to use the = operator in order to assign to a variable you've already declared. The := operator is used to simultaneously declare and assign a variable. The two are the same:

var x int
x = 5
//is the same as
x := 5

This solution will at least compile:

package main

func myfunc() (int, int) {
    return 1, 1
}

func main() {
    var b int
    a := make([]int, 1)
    a[0], b = myfunc()
    a[0] = b
}

To answer your question, I don't think there is a way to simultaneously use an undeclared and a declared variable when returning multiple values. That would be trying to use two different operators simultaneously.

Edit: just saw your example from the code that compiles, so it appears you're already familiar with go's assignment operators. I'll leave the example up anyway.

Upvotes: 10

Related Questions