TL; DR
package main
import "fmt"
type Foo struct{}
func (f *Foo) String() string {
panic("boom")
}
func main() {
fmt.Printf("%s", &Foo{})
}
%!s(PANIC=String method: boom)
はじめに
おなじみ fmt.Printf
では、 %s
を使うことで文字列形式で値を出力できます。
%s
は、string
や []byte
だけでなく、Stringer
の実装(String() string
メソッドを実装している型)に対しても使用可能です。Stringerを %s
すると自動的に String
が呼び出され文字列化されてから出力されます。
Stringがpanicしたら?
ここで、String
メソッド内でpanicした場合の挙動が冒頭のコードです。
func (f *Foo) String() string {
panic("boom")
}
func main() {
fmt.Printf("%s", &Foo{})
}
panicが発生するため文字列は得られませんが、代わりにエラーメッセージが表示されます。この際、fmt.Printf自体は成功しています。
%!s(PANIC=String method: boom)
別の値を一緒に表示してみると分かりやすいです。
panicはせず、panicした際のエラーメッセージが %s
の部分に出力されています。どうやら内部で recoverされて処理が継続しているようです。
func main() {
fmt.Printf("%s: %s", "print foo struct", &Foo{})
}
print foo struct: %!s(PANIC=String method: boom)
処理系の実装を見に行く
裏を取るために fmt
パッケージを読みます。
fmt.Printf
で %s
形式で Stringerを出力すると、この分岐に入ります。
// ...
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
String
呼び出し時にpanicした場合、deferの catchPanic
でrecoverされ、エラーメッセージを文字列として出力しています。これにより、panicをfmtの呼び出し元まで伝播させることなく次の処理に進められます。
おわりに
fmt
パッケージはHello worldにも出てくるので簡単なイメージがありましたが、まだまだ知らない仕様があることを痛感しました。精進せねば...