インドカレーファンクラブ

パソコン、カメラ

【Go】enumっぽいやつのString()メソッド内でpanicを起こした場合、fmtでprintしようとしても握りつぶされる

String()メソッドというか、fmtでprintする時のフォーマットエラーの話

どういうこと

こういう感じ

enumっぽいことをしようとした時に引っかかりそうな問題な気がする
※個人的にはそもそもこのenumっぽいのに不満足だけど今回の話とは関係ないのでスルー

enum_string_err/enum/enum.go

package enum

type Kind int

const (
    _ Kind = iota
    Dog
    Cat
)

func (k Kind) String() string {
    switch k {
    case Dog:
        return "イヌ"
    case Cat:
        return "ネコ"
    }
    panic("Err")
}

enum_string_err/main.go

package main

import (
    "enum_string_err/enum"
    "fmt"
)

func main() {
    var animal enum.Kind
    animal = enum.Dog
    fmt.Println(animal) // イヌ
    animal = enum.Cat
    fmt.Println(animal) // ネコ

    var animalDefault enum.Kind
    fmt.Println(animalDefault) // %!v(PANIC=String method: Err)

    fmt.Println("---") // --- (上のpanicで止まらず出力される!)

    x := animalDefault.String() // これは普通にエラーで止まる
    fmt.Println(x)
}

ということで以下の箇所がエラーにならない

var animalDefault enum.Kind
fmt.Println(animalDefault) // %!v(PANIC=String method: Err)

これは個人的にはなかなか不便な気がするんだけど、 実務のコードではfmtのprintってよりログ出力することが多いからあんまり問題にならない?

本質

この仕様についてはココに書いてあった(panicとかで検索して原文読んで!)
https://golang.org/pkg/fmt/

一応引用

Format errors:

If an invalid argument is given for a verb, such as providing a string to %d, the generated string will contain a description of the problem, as in these examples:

Wrong type or unknown verb: %!verb(type=value)
    Printf("%d", "hi"):        %!d(string=hi)
Too many arguments: %!(EXTRA type=value)
    Printf("hi", "guys"):      hi%!(EXTRA string=guys)
Too few arguments: %!verb(MISSING)
    Printf("hi%d"):            hi%!d(MISSING)
Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
    Printf("%*s", 4.5, "hi"):  %!(BADWIDTH)hi
    Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
Invalid or invalid use of argument index: %!(BADINDEX)
    Printf("%*[2]d", 7):       %!d(BADINDEX)
    Printf("%.[2]d", 7):       %!d(BADINDEX)

If an Error or String method triggers a panic when called by a print routine, the fmt package reformats the error message from the panic, decorating it with an indication that it came through the fmt package. For example, if a String method calls panic("bad"), the resulting formatted message will look like

%!s(PANIC=bad)

The %!s just shows the print verb in use when the failure occurred. If the panic is caused by a nil receiver to an Error or String method, however, the output is the undecorated string, "\<nil>".

この後半の

If an Error or String method triggers a panic when called by a print routine,

のところで今回の話についてピンポイントで触れている