8

我发现在 Go 中提供命名返回变量是一个有用的特性,因为它可以避免单独声明一个或多个变量。但是,在某些情况下,我想为函数中声明的变量返回一个不同的变量作为返回变量。这似乎工作正常,但是我确实觉得声明一个返回变量然后返回其他东西有点奇怪。

在编写一个测试程序来帮助学习 Go(不是下面的那个)时,我发现在返回多个变量的函数的 return 语句中指定返回变量有点烦人。尤其如此,因为变量已在函数声明中命名。我现在在发布此内容时发现,似乎在有命名返回变量的地方,它们不需要在 return 语句中使用,只需“return”就足够了,并且会隐式使用命名变量。我发现这是一个很棒的功能。

所以,虽然我可能已经部分回答了我自己的问题,但有人可以建议我下面的用法是否可以接受?我确定这已记录在案,但我没有遇到它,而且它似乎不在我购买的参考书中,我认为它忽略了此功能。

基本上,规则似乎是(据我所知),在使用命名返回变量的地方,函数语句声明变量,并且函数可以选择隐式使用它们作为返回值,但是这可以是通过使用显式返回值覆盖。

示例程序:

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'"
    }
}
4

3 回答 3

19

你的用法绝对没问题,你会在 Go 源代码中找到很多类似的例子。

我将尝试解释 return 语句在 Go 中的实际工作原理,以更深入地了解原因。思考一下 Go 如何实现函数的参数传递和返回是很有用的。一旦你明白了,你就会明白为什么命名的返回变量如此自然。

函数的所有参数和函数的所有返回值都在 Go 中的堆栈上传递。这与通常在寄存器中传递一些参数的 C 不同。当在 Go 中调用函数时,调用者在堆栈上为参数和返回值腾出空间,然后调用该函数。

具体来说,当这个函数被调用时,它有3个输入参数a、b、c和两个返回值

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

堆栈将如下所示(顶部的低内存地址)

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

现在很明显,命名您的返回参数只是命名堆栈上的那些位置。

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

* a
* b
* c
* x
* y

现在,空return语句的作用也应该很明显了——它只是返回给调用者,无论 x 和 y 的值是什么。

现在进行一些拆卸!编译这个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
}

--- 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     ,

这两个函数汇编成几乎相同的代码。你可以很清楚地看到a,b,c,x,y在堆栈中的声明g,虽然在中f,返回值是匿名的anon3anon4

于 2013-10-05T07:25:02.540 回答
6

注意:CL 20024(2016 年 3 月,适用于 Go 1.7)阐明了命名返回值的用法,并在 go 本身的代码库中说明了何时使用它:

all:在无用时删除公共命名的返回值

命名返回值应该只用于公共函数和方法,当它有助于文档时

如果命名返回值只是在函数体中为程序员节省了几行代码,则不应使用命名返回值,特别是如果这意味着文档中存在口吃,或者它只是在那里程序员可以使用裸返回语句. (除非在非常小的函数中,否则不应使用裸返回)

此更改是对公共 func 签名的手动审核和清理。

如果出现以下情况,则不会更改签名:

  • func 是私有的(不会在公共 godoc 中)
  • 文档引用了它

例如,使用了 archive/zip/reader.go#Open()

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

它现在使用:

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

它的命名返回值没有在其文档中添加任何内容,即:

// Open returns a `ReadCloser` that provides access to the File's contents.
// Multiple files may be read concurrently.
于 2016-03-09T07:58:19.837 回答
0

是的,这是完全可以接受的。我通常使用命名返回变量来确保延迟错误处理中的默认返回,以确保最小可行返回,如下例所示:

//execute an one to one reflection + cache operation
func (cacheSpot CacheSpot) callOneToOne(originalIns []reflect.Value) (returnValue []reflect.Value) {

    defer func() { //assure for not panicking
        if r := recover(); r != nil {
            log.Error("Recovering! Error trying recover cached values!! y %v", r)

            //calling a original function
            returnValue = reflect.ValueOf(cacheSpot.OriginalFunc).Call(originalIns)
        }
    }()

    //... doing a really nasty reflection operation, trying to cache results. Very error prone. Maybe panic

    return arrValues //.. it's ok, arrValues achieved
}
于 2015-10-12T15:19:29.997 回答