46
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Go】 deferに渡した関数内のエラーを呼び出し元に返す

Last updated at Posted at 2020-07-08

はじめに

はじめまして。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などでも以下の様に表示されます。

image.png

ただ、そもそも関数名、引数名と引数の型、戻り値の型などを使って戻り値の意味が連想できないのは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
}

おわりに

基本的に名前付き戻り値は使わずに実装が可能ですが、上記のパターンの場合は使わずに返却することは難しいようでした。
名前付き戻り値の使い所はこういうパターンもあるんですね:thinking:

参考

46
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?