Go言語のdeferを関数で使うテクニックと応用例!初心者でも理解できる遅延実行の使い方
生徒
「Go言語を勉強してると『defer』っていうのが出てきました。これは何に使うんですか?」
先生
「いいところに気がついたね。deferは、ある関数の処理を“あとで実行する”ための仕組みなんだ。主に後片付けに使うよ。」
生徒
「“あとで実行”ってどういうことですか?順番通りに動かないんですか?」
先生
「そう、順番どおりには動かない。deferを使うと、関数の最後にまとめて実行されるんだ。じゃあ、実際の使い方を見てみようか。」
1. deferとは?Go言語における遅延実行の基本
Go言語のdefer(ディファー)は、「関数の最後に実行してほしい処理」を指定するためのキーワードです。たとえば、ファイルを開いたあとに閉じる処理、データベースの接続解除など、最後にやっておくべきことを記述するのに使われます。
deferを使うことで、コードの読みやすさや安全性が高まります。たとえば、ファイルをうっかり閉じ忘れるといったミスを減らすことができます。
2. 基本的なdeferの使い方を見てみよう
それでは、簡単な例でdeferの基本動作を見てみましょう。
package main
import "fmt"
func main() {
fmt.Println("処理を開始します")
defer fmt.Println("最後に実行されます")
fmt.Println("メインの処理中です")
}
このプログラムを実行すると、出力結果は次のようになります:
処理を開始します
メインの処理中です
最後に実行されます
deferで指定したfmt.Println("最後に実行されます")は、main関数の処理が終わる直前に実行されています。
3. deferが便利な場面:ファイルを開いて自動で閉じる
プログラムでファイルを扱うとき、開いたら必ず閉じる必要があります。しかし閉じ忘れはよくあるミスです。そんなときdeferが役立ちます。
次のコードでは、ファイルを開いてから、関数の最後で確実にファイルを閉じています。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("sample.txt")
if err != nil {
fmt.Println("ファイルを開けませんでした:", err)
return
}
defer file.Close()
fmt.Println("ファイルを開いて処理しています")
}
このようにdefer file.Close()と書くことで、関数の最後に自動でファイルを閉じてくれるため、書き忘れを防げます。
4. deferの実行順に注意しよう
複数のdeferを使うと、「最後に書いたものが最初に実行」されます。つまり、後入れ先出し(LIFO)という順序になります。
package main
import "fmt"
func main() {
defer fmt.Println("1番最後")
defer fmt.Println("2番目")
defer fmt.Println("最初に実行される")
}
このコードの出力結果は次のとおりです:
最初に実行される
2番目
1番最後
deferは書いた順ではなく逆順に実行されることを覚えておきましょう。
5. 関数でdeferを活用するテクニック
関数内でdeferを使うことで、処理の片付けやログ出力などを効率よく行えます。次の例は、「処理の開始」と「処理の終了」をログとして出力するものです。
package main
import "fmt"
func main() {
process()
}
func process() {
fmt.Println("処理開始")
defer fmt.Println("処理終了")
fmt.Println("データを処理しています...")
}
このように、関数の入り口と出口に関係する処理を簡潔に書けるのが、deferの大きな利点です。
6. deferの注意点と使いすぎのデメリット
便利なdeferですが、注意点もあります。
- 性能に影響する:
deferは毎回スタックに積まれるため、頻繁に使うとわずかですが処理速度に影響があります。 - ループ内で多用しない:ループの中で毎回
deferすると、メモリ使用量が増えることがあります。
使いどころを考えて、主に「関数の最後に絶対に実行しておきたい処理」に使うのがベストです。
まとめ
Go言語におけるdeferは、プログラムの後片付けやリソース管理を安全かつ確実に行うための非常に重要な機能であり、初心者の段階で理解しておくと開発が格段にスムーズになります。この記事を通して、deferの基本的な動作、関数の終了時に実行される特徴、複数回使用した場合の逆順実行(LIFO)といった仕組みを丁寧に学ぶことができました。また、実際にファイルを開く処理やログ管理など、実務でよく使われる場面に応用できる知識として身につけられたはずです。
プログラムを書く際には、その時点で開いたリソースが必ず適切に閉じられるようにすることが非常に重要です。たとえば、ファイル、ネットワーク接続、データベース接続など、忘れると大きな問題につながる処理は多く存在します。そのような状況でdeferが役に立ち、関数の最後に自動で実行される後片付け処理として機能します。これにより、コードの見通しが良くなり、安全性も高まるのです。
また、今回の記事の中では、関数内でのdefer活用例、複数のdeferを使用した場合の実行順序、ループ内での使いすぎに注意すべき理由など、実際に使う場面で理解しておきたい応用知識も紹介しました。こうした知識は、Go言語を学ぶ初心者がステップアップするうえで欠かせません。
以下では、今回学んだ内容をさらに深めるために、deferを複数の関数に応用し、ログ出力・エラー処理・複数リソースの管理を組み合わせた少し高度なサンプルコードを掲載します。ぜひ参考にして、実際の開発にも活かしてみてください。
追加サンプルコード:複数リソースとログ管理にdeferを使う応用例
package main
import (
"fmt"
"os"
)
// ログを開始・終了で出力する関数
func withLog(processName string) func() {
fmt.Println("=== 開始:", processName, "===")
return func() {
fmt.Println("=== 終了:", processName, "===")
}
}
// ファイルを安全に処理する関数
func handleFile(path string) {
defer withLog("ファイル処理")()
file, err := os.Open(path)
if err != nil {
fmt.Println("ファイルを開けませんでした:", err)
return
}
defer file.Close()
fmt.Println("ファイルを安全に扱っています:", path)
}
func main() {
defer withLog("メイン処理")()
handleFile("sample.txt")
fmt.Println("その他の処理を実行中...")
}
このコードでは、ログ出力を管理するためのwithLog関数を使い、関数の開始と終了をわかりやすく記録しています。また、ファイル操作でもdefer file.Close()を使うことで、処理の中断が発生しても確実にリソースが解放されます。
deferを使う最大のメリットは、複雑な処理の中でも「最後に必ず実行したいこと」を自然な形で書ける点です。これにより、コードの保守性が上がり、想定外のエラーが起きてもプログラム全体が安定して動作します。とくに、プロジェクトが大きくなっていくとこうしたリソース管理が重要になるため、初心者のうちから正しい使い方に慣れておくことがとても大切です。
deferは一見すると単純なキーワードですが、理解すればするほど奥深く、さまざまな場面で役に立つ強力な仕組みです。今回学んだことを活かし、より安全で読みやすく、ミスの少ないGo言語のコードを書けるよう意識してみてください。
生徒
「deferって単純に“最後に動く”だけだと思ってたんですが、応用するとこんなに便利なんですね!」
先生
「その通りです。リソース管理やログ管理に使うと、コードが安全でスッキリしたものになりますよ。」
生徒
「ファイル操作の例はすごくわかりやすかったです。閉じ忘れを防げるのは助かります!」
先生
「実際の開発では“閉じ忘れ”が深刻な問題になることもあるので、deferはとても大切ですよ。」
生徒
「今回のサンプルみたいにログも組み合わせると便利ですね。もっと使いこなせるように練習してみます!」