💯

Goクイズ 〜Named return valuesについて〜

2020/12/23に公開

この記事は DeNA 20 新卒 Advent Calendar 2020 23日目の記事です。

最近流行りの?Goクイズの出題に挑戦してみます!

問題

以下の実行結果はどうなるでしょうか

package main

import "fmt"

func main() {
  run()
}

func run() (err error) {

  defer func() {
    if err != nil {
      fmt.Println(err)
    }
  }()

  if err := fmt.Errorf("Error caught!"); err != nil {
    return
  }

  return
}
  1. コンパイルエラー
  2. "Error caught!" が出力される
  3. 何も出力されない

正解と解説

正解

正解は1です。
コンパイルエラーになります。

解説

この件はGoのspecに以下のような記述があります。

Implementation restriction: A compiler may disallow an empty expression list in a "return" statement if a different entity (constant, type, or variable) with the same name as a result parameter is in scope at the place of the return.
https://golang.org/ref/spec#Return_statements

関数内の別スコープで名前付き戻り値(Named return values)に指定した名前と同じ変数や定数、型を宣言し、そのスコープ内で空のreturnをしてしまうと、どちら(関数を定義したスコープで宣言されたものか、その別スコープで宣言したものか)を優先するべきかわからないためコンパイルエラーが投げられるようです。

確かに、逆にどちらかを優先する言語仕様にしてしまうと混乱の元となるためコンパイルエラーにしてしまったほうが親切そうですね。

今回の場合、以下のようにreturnする値を明示するとコンパイルが通り"Error caught!"が出力されます。

  if err := fmt.Errorf("Error caught!"); err != nil {
    return err
  }

(ちなみにdeferは惑わすための引っ掛けです)

おまけ問題

Named return valuesの話からは少し逸れますが、以下を実行した場合の結果はどうなるでしょうか。

package main

import "fmt"

func main() {
  run()
}

func run() error {

  var err error
  defer func() {
    if err != nil {
      fmt.Println("Error caught!")
    }
  }()

  if err := fmt.Errorf("hoge error"); err != nil {
    return err
  }

  return nil
}

  1. コンパイルエラー
  2. "Error caught!"が出力される
  3. 何も出力されない

おまけの正解と解説

正解

正解は3です。何も出力されません。

解説

defer文内のerrはvar err errorのerrを指すため、if文のスコープで定義されたerrは関係ありません。よって何も出力されません。

とくに引っ掛けのないおまけ問題ですが、意外と"Error caught!"が出力される前提で実装してしまいがちなので気をつけていきたいです。

さいごに

DeNA 公式 Twitter アカウント @DeNAxTechでは、この Advent Calendar や DeNA の技術記事、イベントでの登壇資料などを発信しています。 もしよろしければフォローしてみてください!

Discussion