goで文字列ベースのenumの定義はこんな感じが良いかもしれない
文字列ベースのenumの定義で良さそうなコードができたのでメモ(iotaを使った数値がベースのものではない)。
文字列ベースのもの
個人的には、特に速度やメモリーなどのことを気にしない場合には、文字列ベースのもののほうが取り回しやすくて良いと思っている。
以下の実装はfmt.Stringerに対応しているし、JSONからの読み込みにも対応している。
pkg/errorsを使っているが、そろそろxerrorsでも良いかもしれない。個人的には、予期せぬ値でエラーになったときに、どんな値だったのかが明らかになっていてほしい。そんなわけでwrapしている。errors.Cause()
越しにbaseのエラー値にアクセスできると便利なこともありそうということでErrInvalidOpType
を定義している。
以下はAdd, Mul, Subの3つの可能性があるようなenumの定義の例。
package enums import ( "encoding/json" "fmt" "strings" "github.com/pkg/errors" ) type Op string const ( OpAdd Op = "Add" OpSub = "Sub" OpMul = "Mul" ) var ErrInvalidOpType = fmt.Errorf("invalid op type") func (v Op) String() string { return string(v) } func (v Op) Valid() error { switch v { case OpAdd, OpSub, OpMul: return nil default: return errors.Wrapf(ErrInvalidOpType, "get %s", v) } } func (v *Op) UnmarshalJSON(b []byte) error { *v = Op(strings.Trim(string(b), `"`)) return v.Valid() } func ParseOp(s string) (v Op, err error) { v = Op(s) err = v.Valid() return }
ちなみに、UnmarshalJSON()
メソッドを定義しなくても、正しい値だけがやってくる場合には動作はするのだけれど。バリデーションも入れたいので。ここの部分。
func (v *Op) UnmarshalJSON(b []byte) error { *v = Op(strings.Trim(string(b), `"`)) return v.Valid() }
iotaベースのもの
内部で使うだけのものなら、iotaベースのもので十分だと思う。こういう感じで定義するのが楽かもしれない。
import "fmt" type Op uint8 const ( OpAdd Op = iota + 1 OpSub OpMul ) var ( _ops = [...]string{"", "Add", "Sub", "Mul"} ) func (v Op) String() string { return _ops[v] }
(String()
の中に直接_ops部分を書いてしまっても良い感じに最適化されたりするんだろうか(調べていない))
参考
参考にしたのはものは以下のもの。IsValid()
がerrorを返すのは気持ち悪いので Valid()
にした。
ParseOp()
はstrconvに合わせたインターフェイスなのでこういう名前で良いと思う。あるいはMustOpのようなものも欲しくなることがあるかもしれない。
- Working with enums in Go https://www.ribice.ba/golang-enums/
iotaベースのものならuberのものが参考になる。iotaを使ったときのゼロ値を気にしようということで。
- Uber Go Style Guide https://github.com/uber-go/guide/blob/master/style.md#start-enums-at-one
- 4 iota enum examples https://yourbasic.org/golang/iota/