はじめに
はじめまして。Go初学者です。
Gopher道場#8や自身の勉強にて、気づいた学びを共有しています。
今回は、defer
内でのエラーハンドリング方法と、名前付き戻り値の利用方針についてまとめました。
結論から
deferに渡した関数のreturnは無視されるので、deferに渡した関数のエラーは 名前付き戻り値
に代入して呼び出し元に戻す。
If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)
deferred関数に戻り値がある場合は、関数が完了したときに破棄されます。
defer
is 何
いわゆる Finally
句の様なもので、関数が終了する前に呼ばれます。
defer
キーワードの後に関数を渡すと、渡した順にスタックされていき、最後から順に実行されていきます。
func main() {
defer func() {
fmt.Println("defer 1")
}()
defer func() {
fmt.Println("defer 2")
}()
fmt.Println("hoge")
}
hoge
defer 2
defer 1
panicの場合でも、きちんと呼ばれてくれます。
func main() {
defer func() {
fmt.Println("defer 1")
}()
defer func() {
fmt.Println("defer 2")
}()
panic("panic!")
}
defer 2
defer 1
panic: panic!
goroutine 1 [running]:
main.main()
/tmp/sandbox481037905/prog.go:15 +0x68
Program exited: status 2.
以下の様に、ファイルのクローズ処理などによく用いられるそうです。
func main() {
file, _ := os.Create("hoge.txt")
defer file.Close()
// 書き込み処理
}
defer内でのreturn
は無視される
下記の様な記述では、呼び出し元はerr
を受け取る事ができません。
func main() {
if err := hoge(); err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
func hoge() error {
defer func() error {
return fmt.Errorf("error occured!")
}()
return nil
}
では、先ほどの例でファイル操作をしている場合などに、エラー判定を呼び出し元に返すにはどのようにしたらよいのか?
この際に、名前付き戻り値を利用します。
名前付き戻り値
is 何
Goでは戻り値に名称をつける事ができます。
また、付けられた名称は変数として関数内で利用する事が可能です。
名前付き戻り値を利用すると、return
のみでも名前を付けた変数が返却することができます。
func square(i int) (squared int) {
squared = i * i
return
}
良い所
戻り値に名前をつける事が出来るので、関数の利用者は作成者が何を返したいのかより明確になります。
IDEなどでも以下の様に表示されます。
ただ、そもそも関数名、引数名と引数の型、戻り値の型などを使って戻り値の意味が連想できないのはBadかなとは思いますが...
気になる所
私個人的には、return
で省略できる点については、ソースの可読性を下げる恐れがあると感じています。
なので、実際はreturn squared
の様に、明示的に記載することにしています。
deferに渡した関数内のエラーを呼び出し元に返す
本題です。
以下の様に、defer内で名前付き戻り値にエラーを設定することで、エラーを呼び出し元に返却する事が可能になります。
func main() {
if err := file("hoge"); err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
func file(fileName string) (err error) { // errorに名前をつける
file, _ := os.Create(fileName)
defer func() {
err = file.Close() // errに処理結果を代入
}()
// 書き込み処理
return nil
}
おわりに
基本的に名前付き戻り値は使わずに実装が可能ですが、上記のパターンの場合は使わずに返却することは難しいようでした。
名前付き戻り値の使い所はこういうパターンもあるんですね