Thursday, October 10, 2013

Functions and Naked Returns In Go

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.

6 comments:

  1. Interesting post with some good hints as to minor things that could easily become "gotchya"s.

    In particular, after reading the part about defer statements, I played around with them a bit and was surprised to find that the following two functions returned the same value:


    func add(x,y int) (z int) {
    x =2
    defer func(a int){
    z = a + y
    }(x)
    return
    }

    func add(x,y int) (z int) {
    defer func(){
    z = x + y
    }()
    x =2
    return
    }

    While the following function returned a separate value:

    func add(x,y int) (z int) {
    defer func(a int){
    z = a + y
    }(x)
    x =2
    return
    }

    Before reading this post, I had only used defer as a means of making sure I call file.Close() or a similar function, I had never even considered it for any other use.

    Another interesting thing I found is that this function returns 0:

    func add(x,y int) (z int) {
    defer func() {
    z = 0
    }()
    return x + y
    }

    While this function returns the sum:

    func add(x,y int) (z int) {
    return x + y
    }

    I can figure out why this happens, but like I said earlier: There are a lot of little "gotchya"s to watch out for.

    ReplyDelete
    Replies
    1. Sometimes the things that seem simple give us the biggest problems. I never expected the G+ post to be filled with such passion about this topic. Thanks for the comment.

      Delete
  2. > Error handling if performed by functions returning a value of type error and the calling function evaluating that value before continuing.

    The calling function might compare the return value to nil, use it in a type assertion or call the Error method. What the calling function does not do is evaluate the error. The error is not a function.

    > I remove the colon (:) from the assignment operation.

    The ":=" is a short variable declaration, not an assignment. The "=" is assignment.

    > If you are passing raw values out of your function

    What makes a value "raw"?

    ReplyDelete
    Replies
    1. Thanks for the grammar problems. I made a few changes to fix that!! I think people understand that when I say raw I mean passing a direct value and not using a variable. What language would you use?

      Delete
  3. David Cheney comments on G+ that if every single return argument is named, ..."it is usually a code smell." Do you know if Go vet will have this consensus in the near future? Your post broke it down for me. It is understandable, but like Bryan Conklin explains, there are best practice use cases of when it should be named and when is best to return naked.

    ReplyDelete
    Replies
    1. I tried to give you some guidance at the end without specifically saying do this and that. In our Meetup I provide you more of my experience and preferences.

      What Dave was explaining was the last example I gave you. Where you returned a raw value and still named the return argument. That is following a pattern of naming return arguments for the sake of following a pattern.

      I have not played with Go vet nor do I know the direction. Why not ask that question on the G+ post for the article?

      Delete