In Go values that are returned from functions are passed by value. Go gives you some nice flexibility when it comes to returning values from a function.

Here is a simple example of returning two values from a function:

package main

import (
   "fmt"
)

func main() {
   id, err := ReturnId()

   if err != nil {
      fmt.Printf("ERROR: %s", err)
      return
   }

   fmt.Printf("Id: %d\n", id)
}

func ReturnId() (int, error) {
   id := 10
   return id, nil
}

The function ReturnId returns a value of type integer and of type error. This is something very common that is done in Go. Error handling is performed by returning a value of type error from your functions and the calling function evaluating that value before continuing.

If you don't care about the error for some reason after a function call returns, you can do something like this:

   id, _ := ReturnId()

This time I used an underscore to represent the return value for the second return argument, which was the error. This is really nice because I don't need to declare a variable to hold the value being passed in, I can simply ignore it.

You also have the option to name your return arguments:

func ReturnId() (id int, err error) {
   id = 10
   return id, err
}

If you name your return arguments you are creating local variables just like with your function parameters. This time when I set the id variable, I remove the colon (:) from the short variable declaration and convert it to an assignment operation. Then in the return I specify the return variables as normal.

Naming your return arguments is a nice way to document what you are returning. There is also something else that you can do with your named arguments, or not do:

func ReturnId() (id int, err error) {
   id = 10
   return
}

This is what is called a naked return. I have removed the arguments from the return statement. The Go compiler automatically returns the current values in the return arguments local variables. Though this is really cool you need to watch for shadowing:

func ReturnId() (id int, err error) {
   id = 10

   if id == 10 {
      err := fmt.Errorf("Invalid Id\n")
      return
   }

   return
}

If you try to compile this you will get the following compiler error:

err is shadowed during return

To understand why this error exists you need to understand what curly bracket do inside of a function. Each set of curly brackets define a new level of scope. Take this code for example:

func main() {
   id := 10
   id := 20

   fmt.Printf("Id: %d\n", id)
}

If you try to compile this code you get the following error:

no new variables on left side of :=

This makes sense because you are trying to declare the same variable name twice. The error goes away if we change the code to look like this:

func main() {
   id := 10

   {
       id := 20
       fmt.Printf("Id: %d\n", id)
   }

   fmt.Printf("Id: %d\n", id)
}

The curly brackets define a new stack frame and therefore a new level of scope. The variable name can be reused inside the new set of curly brackets. When the code reaches the closing curly bracket that small piece of the stack is popped.

Look again at the code that caused the shadowing error:

func ReturnId() (id int, err error) {
   id = 10

   if id == 10 {
      err := fmt.Errorf("Invalid Id\n")
      return
   }

   return
}

Inside the if statement we are creating a new variable called err. We are not using the err variable declared as the function return argument. The compiler recognizes this and produces the error. If the compiler did not report this error, you would never see the error that occured inside the if statement. The return err variable is what is passed by default

Naming your return arguments come in real handy when using a defer statement:

func ReturnId() (id int, err error) {
   defer func() {
      if id == 10 {
         err = fmt.Errorf("Invalid Id\n")
      }
   }()

   id = 10

   return
}

Because the return arguments are named, you can reference them in the defer function. You can even change the value of the return arguments inside the defer call and the calling function will see the new values. This version will display the error message.

You need to be aware that the defer statement is evaluated inline with the rest of the code:

func ReturnId() (id int, err error) {
   defer func(id int) {
      if id == 10 {
         err = fmt.Errorf("Invalid Id\n")
      }
   }(id)

   id = 10

   return
}

This version does not display the error message. The value of id is not 10 until after the defer statement is evaluated.

Sometimes it makes sense to use named return arguments, such when using a defer statement at the top of your function. If you are passing raw values out of your function then something like this does not make sense:

package main

import (
   "fmt"
)

func main() {
   ans := AddNumbers(10, 12)
   fmt.Printf("Answer: %d\n", ans)
}

func AddNumbers(a int, b int) (result int) {
   return a + b
}

The return argument is named for the AddNumbers function but never used. Instead we return the answer of the operation directly out of the return. This shows you how you can still return any value you want even if you name the return arguments.

I asked the Go community for their opinions about using named arguments and naked returns:

https://plus.google.com/107537752159279043170/posts/8hMjHhmyNk2

I got a very good mix of opinions that I think everyone should read. Go gives you a lot of flexibility and choice when building your functions. Don't look for a single common practice for everything. Take each function individually and implement the best construct for that use case.

Trusted by top technology companies

We've built our reputation as educators and bring that mentality to every project. When you partner with us, your team will learn best practices and grow along the way.

30,000+

Engineers Trained

1,000+

Companies Worldwide

12+

Years in Business