今回はGoでビットを利用してフラグ管理を行う際の実装パターンを紹介します
go/toolsのssa.BuilderModeはこの方法を用いて実装されています。
(というかこのBuilderModeの実装を読んでもらえればこの記事で紹介することは終わりです)
実装を見てみよう
type BuilderMode uint
const (
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
PrintFunctions // Print function SSA code to stdout
LogSource // Log source locations as SSA builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel.
GlobalDebug // Enable debug info for all packages
BareInits // Build init functions without guards or calls to dependent inits
)
こんな感じの実装になってます、modeそれぞれの意味は読まなくて大丈夫です。
Goでenumを作成するときにお馴染みのiota
が出てきていますね。
The value of iota is reset to 0 whenever the reserved word const appears in the source (i.e. each const block) and incremented by one after each ConstSpec e.g. each Line. This can be combined with the constant shorthand (leaving out everything after the constant name) to very concisely define related constants.
というやつです、詳しくはこちら
iotaによって、上記のBuilderModeは2進数で表すと
1
10
100
1000
10000
...
というふうに定義されていきます。それぞれの桁で各モードのON/OFFが表現されることになりますね。
modeの管理を行う関数を定義する
BuilderModeの指定を行う際に一々計算をしているとめんどくさく分かりづらいです。BuilderModeの実装にはビット和の演算を用いたSet
メソッドが生えています。
実装を見てみましょう
// Set parses the flag characters in s and updates *m.
func (m *BuilderMode) Set(s string) error {
var mode BuilderMode
for _, c := range s {
switch c {
case 'D':
mode |= GlobalDebug
case 'P':
mode |= PrintPackages
case 'F':
mode |= PrintFunctions
case 'S':
mode |= LogSource | BuildSerially
case 'C':
mode |= SanityCheckFunctions
case 'N':
mode |= NaiveForm
case 'L':
mode |= BuildSerially
case 'I':
mode |= BareInits
default:
return fmt.Errorf("unknown BuilderMode option: %q", c)
}
}
*m = mode
return nil
}
string型のsが渡されて、その文字によってフラグがONになっていることがわかります。
また、文字の扱いに関しては以下のように説明があります。
const BuilderModeDoc = `Options controlling the SSA builder.
The value is a sequence of zero or more of these letters:
C perform sanity [C]hecking of the SSA form.
D include [D]ebug info for every function.
P print [P]ackage inventory.
F print [F]unction SSA code.
S log [S]ource locations as SSA builder progresses.
L build distinct packages seria[L]ly instead of in parallel.
N build [N]aive SSA form: don't replace local loads/stores with registers.
I build bare [I]nit functions: no init guards or calls to dependent inits.
`
Setメソッドを用いて以下のようにModeの変更が可能です。
mode.Set("D") // GlobalDebug(D)が有効に
mode.Set("DP") // GlobalDebug(D)、PrintPackages(P)が有効に
String()メソッドもはやしておく
func (m BuilderMode) String() string {
var buf bytes.Buffer
if m&GlobalDebug != 0 {
buf.WriteByte('D')
}
if m&PrintPackages != 0 {
buf.WriteByte('P')
}
if m&PrintFunctions != 0 {
buf.WriteByte('F')
}
if m&LogSource != 0 {
buf.WriteByte('S')
}
if m&SanityCheckFunctions != 0 {
buf.WriteByte('C')
}
if m&NaiveForm != 0 {
buf.WriteByte('N')
}
if m&BuildSerially != 0 {
buf.WriteByte('L')
}
if m&BareInits != 0 {
buf.WriteByte('I')
}
return buf.String()
}
これによってログなどに出力した時に先程の文字列が出力されるようになっています
a := BuilderMode(0)
a.Set("PD")
fmt.Print(a) // PDが出力される
どう言った場面で有効な手段か
そのままですがboolのoption(フラグ)を複数持たせて振る舞いを変えさせたい場合に有効です。
ただし、一つの関数や構造体などにoptionを実装し、振る舞いを変えまくるより普通に複数の関数/構造体などに作り分けた方がわかりやすい場合もあると思います。
終わりに
今回はビットによるフラグ管理を利用したOptionの作成を紹介しました。知ってる人にとっては、はいはいあれねっていう感じだったかもしれません。
久しぶりに技術記事書いてえらい