Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the intended usage of named return variables in Go?

Tags:

go

I find the provision of named returned variables in Go to be a useful feature, because it can avoid the separate declaration of a variable or variables. However, in some instances I want to return a different variable to the variable declared in the function as the return-variable. That appears to work ok, however I do find it a little strange to declare a return variable and then return something else.

While writing a test program to help learn Go (not the one below), I found it a little annoying specifying the return variable in the return statement of functions returning multiple variables. Particularly so, because the variables had been named in the function declaration. I now find while posting this, that it appears that where there are named return variables, they don't need to be used in the return statement, just "return" will suffice and will implicitly use the named variables. That I find this a great feature.

So, although I have possibly partly answered my own question, could someone advise if my usage below is acceptable? I'm sure this is documented, but I haven't come across it, and it doesn't appear to be in the reference-book that I purchased which I think overlooks this feature.

Basically, the rule appears to be (as far as I can determine), that where named return variables are used, that the function statement declares the variables, and also the function can optionally implicitly uses them as the return values, however this can be overridden by using explicit return values.

Example Program :

package main

func main() {
    var sVar1, sVar2 string
    println("Test Function return-values")
    sVar1, sVar2 = fGetVal(1)
    println("This was returned for '1' : " + sVar1 + ", " + sVar2)
    sVar1, sVar2 = fGetVal(2)
    println("This was returned for '2' : " + sVar1 + ", " + sVar2)
}

func fGetVal(iSeln int) (sReturn1 string, sReturn2 string) {
    sReturn1 = "This is 'sReturn1'"
    sReturn2 = "This is 'sReturn2'"

    switch iSeln {
        case 1  :  return
        default : return "This is not 'sReturn1'", "This is not 'sReturn2'"
    }
}
like image 795
Brian Oh Avatar asked Oct 05 '13 06:10

Brian Oh


2 Answers

Your usage is absolutely fine and you'll find plenty of similar examples in the Go source code.

I'll attempt to explain how the return statement actually works in Go to give a deeper appreciation of why. It is useful to have a think about how Go implements parameter passing and return from functions. Once you understand that you'll understand why named return variables are so natural.

All arguments to functions and all return values from functions are passed on the stack in Go. This varies from C which usually passes some parameters in registers. When a function is called in Go the caller makes space on the stack for both the arguments and the return values then calls the function.

Specifically, when this function is called, which has 3 input parameters a, b, c and two return values

func f(a int, b int, c int) (int, int)

The stack will look like this (low memory address at the top)

* a
* b
* c
* space for return parameter 1
* space for return parameter 2

Now it is obvious that naming your return parameter just names those locations on the stack.

func f(a int, b int, c int) (x int, y int)

* a
* b
* c
* x
* y

It should now also be obvious what an empty return statement does - it just returns to the caller with whatever the values of x and y are.

Now for some disassembly! Compiling this with go build -gcflags -S test.go

package a

func f(a int, b int, c int) (int, int) {
    return a, 0
}

func g(a int, b int, c int) (x int, y int) {
    x = a
    return
}

Gives

--- prog list "f" ---
0000 (test.go:3) TEXT    f+0(SB),$0-40
0001 (test.go:3) LOCALS  ,$0
0002 (test.go:3) TYPE    a+0(FP){int},$8
0003 (test.go:3) TYPE    b+8(FP){int},$8
0004 (test.go:3) TYPE    c+16(FP){int},$8
0005 (test.go:3) TYPE    ~anon3+24(FP){int},$8
0006 (test.go:3) TYPE    ~anon4+32(FP){int},$8
0007 (test.go:4) MOVQ    a+0(FP),BX
0008 (test.go:4) MOVQ    BX,~anon3+24(FP)
0009 (test.go:4) MOVQ    $0,~anon4+32(FP)
0010 (test.go:4) RET     ,

--- prog list "g" ---
0011 (test.go:7) TEXT    g+0(SB),$0-40
0012 (test.go:7) LOCALS  ,$0
0013 (test.go:7) TYPE    a+0(FP){int},$8
0014 (test.go:7) TYPE    b+8(FP){int},$8
0015 (test.go:7) TYPE    c+16(FP){int},$8
0016 (test.go:7) TYPE    x+24(FP){int},$8
0017 (test.go:7) TYPE    y+32(FP){int},$8
0018 (test.go:7) MOVQ    $0,y+32(FP)
0019 (test.go:8) MOVQ    a+0(FP),BX
0020 (test.go:8) MOVQ    BX,x+24(FP)
0021 (test.go:9) RET     ,

Both functions assemble to pretty much the same code. You can see quite clearly the declarations of a,b,c,x,y on the stack in g, though in f, the return values are anonymous anon3 and anon4.

like image 94
Nick Craig-Wood Avatar answered Oct 24 '22 05:10

Nick Craig-Wood


Note: CL 20024 (March 2016, for Go 1.7) clarifies the usage of named return values and illustrates within the code base of go itself when its usage is appropriate:

all: remove public named return values when useless

Named returned values should only be used on public funcs and methods when it contributes to the documentation.

Named return values should not be used if they're only saving the programmer a few lines of code inside the body of the function, especially if that means there's stutter in the documentation or it was only there so the programmer could use a naked return statement. (Naked returns should not be used except in very small functions)

This change is a manual audit & cleanup of public func signatures.

Signatures were not changed if:

  • the func was private (wouldn't be in public godoc)
  • the documentation referenced it

For instance, archive/zip/reader.go#Open() used

func (f *File) Open() (rc io.ReadCloser, err error) {

It now uses:

func (f *File) Open() (io.ReadCloser, error) {

Its named return values didn't add anything to its documentation, which was:

// Open returns a `ReadCloser` that provides access to the File's contents.
// Multiple files may be read concurrently.
like image 7
VonC Avatar answered Oct 24 '22 03:10

VonC