Wednesday, June 15, 2011

Multiple return values from Go functions

Go allows you to have a function that returns multiple values. A function definition that returns three values would be defined similar to:

func SumProdDiff(i, j int) (int, int, int)

Since there is more than one return value, it is necessary to enclose it within parentheses. To return values from a function such as the above, you would have a return statement with three comma separated values.

return sum, prod, diff

And we can accept it from within the calling code with an assignment operator as:

s, p, d := SumProdDiff(value1, value2)

If you had lesser number of values in the return statement than is defined in the function signature, then you will see a not enough arguments to return error.

In the definition of the function, it is also possible to name each of the return values. We shall see the usage of it later, but the definition would be similar to:

func SumProdDiff(i, j int) (s int, p int, d int)

Multiple return values

We’ll implement the above example in code. To the method we pass two integers and we expect to get back the sum, product, and difference of the two.

Full program
package main

import (
    "fmt"
)

func SumProductDiff(i, j int) (int, int, int) {
    return i+j, i*j, i-j
}

func main() { 
    sum, prod, diff := SumProductDiff(3,4)
    fmt.Println("Sum:", sum, "| Product:",prod, "| Diff:", diff)
}

Sum: 7 | Product: 12 | Diff: -1

Multiple return values and error handling

Multiple return values gives us a different way of checking for errors. The typical paradigm is:

Partial code
retValue, err := my_function()
if err == nil { //go ahead with normal code 
} else { //error handling code
}

In the following program, we implement our own version of square root of a number. Since square root of a negative number is invalid, we return an error in that case. The return value is then checked in the calling program before continuing the program. In the code below, we have expanded the error handling and repeated it for the purpose of illustration, but of course, it could be modularized.

Full program
package main

import (
    "fmt"
    "errors"
    "math"
)

func MySqrt(f float64) (float64, error) {
    //return an error as second parameter if invalid input
    if (f < 0) {
        return float64(math.NaN()), errors.New("I won't be able to do a sqrt of negative number!")
    }

    //otherwise use default square root function
    return math.Sqrt(f), nil
}

func main() { 
    fmt.Print("First example with -1: ")
    ret1, err1 := MySqrt(-1)
    if err1 != nil {
        fmt.Println("Error! Return values are", ret1, err1)
    } else {
        fmt.Println("It's ok! Return values are", ret1, err1)
    }

    fmt.Print("Second example with 5: ")
    //you could also write it like this
    if ret2, err2 := MySqrt(5); err2 != nil {
        fmt.Println("Error! Return values are", ret2, err2)
    } else {
        fmt.Println("It's ok! Return values are", ret2, err2)    }
}

First example with -1: Error! Return values are NaN I won't be able to do a sqrt of negative number!
Second example with 5: It's ok! Return values are 2.23606797749979

Named return variables in function definition

Go allows you to name the return variables in the function signature. This allows you to use those variable names itself within your code. When you later have a return statement, it is not necessary to mention the variable names as Go automatically knows that the named return variables are the ones to be sent back. As of now, the last return statement still needs to be written, and the compiler will throw an error if you don’t.

The return variables that you name in the function signature automatically are zero-ed. This means to say that they have their default initialization values - numbers like integers are floats are set to 0, strings are empty, etc. Structs similarly are zero-ed based on its constituent fields.

Full program
package main

import (
    "fmt"
    "errors"
    "math"
)

//name the return variables - by default it will have 'zero-ed' values i.e. numbers are 0, string is empty, etc.
func MySqrt2(f float64) (ret float64, err error) {
    if (f < 0) {
        //then you can use those variables in code
        ret = float64(math.NaN())
        err = errors.New("I won't be able to do a sqrt of negative number!")
    } else {
        ret = math.Sqrt(f)
        //err is not assigned, so it gets default value nil
    }
    //automatically return the named return variables ret and err
    return
}

func main() { 
    fmt.Println(MySqrt2(5))
}

2.23606797749979 <nil>

Do remember that you can explicitly change the values that are returned. If you specify a different return statement, like say return 5, nil, then the named variable is ignored and the explicitly returned values are what is sent back.

12 comments:

  1. Partial code
    retValue, err := my_function()
    if err != nil { //go ahead with normal code
    } else { //error handling code
    }

    This should be the other way around, like:
    if err != nil { //error handling code
    } else { //go ahead with normal code
    }

    ReplyDelete
  2. Thank you Ivo, I've updated the check with "if err == nil" to give the same result as you pointed out.

    ReplyDelete
    Replies
    1. Hi Sathish dont you think it is redundant to use this :
      if err == nil {
      }else{
      }

      Instead all you need is
      if err {//Error code
      }else{// Else normal code
      }

      Delete
  3. Hi,
    First, thanks for this great tutorial. I had some problems with the error handling on the sqrt example. I think the MySqrt func needs to look like :

    import ..
    errors

    func MySqrt(f float64) (float64, error) {
    if (f<0) {
    return float64(math.Nan()), errors.New("Cant ...")
    }
    return math.Sqrt(f), nil

    Eclipse Go plugin was giving me compilation errors with os.NewError and I couldn't find it in the documentation here :
    http://golang.org/pkg/os/

    Thanks again.

    Pete

    ReplyDelete
    Replies
    1. Thank you Peter. I checked it based on your feedback and have updated it.

      Delete
  4. Out of curiosity, can you do this: `foo (bar ())` where

    foo := func (a int, b error)
    and
    bar := func () (a int, b error)

    ?

    ReplyDelete
    Replies
    1. Yes, actually. I'm still deciding whether or not that's a terrible, terrible thing though.

      Delete
    2. This is really cool! Nesting functions in functions, you can even go another step futher...

      What would be the best thing to do error handling, throwing it up to the main function or declare a err variable and do callbacks? :D

      Delete
  5. sorry, but when exactly did the time for improving Golang pass?

    Perhaps "logical" is not the best word, but rather "pragmatic" in that it is reasonable to expect the binding of arguments to return values to work consistently no matter the location of the arguments in the function's signature.
    PHP Training in Chennai |
    Pega Training in Chennai

    ReplyDelete
  6. Greens Technology's the leading software Training & placement centre Chennai & ( Adyar)
    Manual Testing training in chennai

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.