【问题标题】:What is the intended usage of named return variables in Go?Go 中命名返回变量的预期用途是什么?
【发布时间】:2013-10-05 06:19:06
【问题描述】:

我发现在 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'"
    }
}

【问题讨论】:

    标签: go


    【解决方案1】:

    注意:CL 20024 (March 2016, for 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.
    

    【讨论】:

      【解决方案2】:

      你的用法绝对没问题,你会在 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,yg的堆栈上的声明,虽然在f中,返回值是匿名的anon3anon4

      【讨论】:

      • 感谢您的详细解释。
      • 有兴趣了解命名的返回值在堆栈中。
      【解决方案3】:

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

      //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
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-22
        • 2012-06-07
        • 1970-01-01
        相关资源
        最近更新 更多