今回は 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 の作成を紹介しました。知ってる人にとっては、はいはいあれねっていう感じだったかもしれません。
久しぶりに技術記事書いてえらい