実践Go言語(Effective Go)の翻訳、8回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。
初期化
初期化において、Go言語とCやC++言語では見かけ上それほど差がないように見えますが、Go言語の初期化はより強力です。複合構造体は初期化を行いながら構築することが可能です。また異なるパッケージ間においてもオブジェクトの初期化順序は正しく取り扱われます。
定数
Go言語における定数は、その名の通り「定数」です。定数はコンパイル時に作成されます。これは定数が関数内でローカルに定義されているときも同様です。ただし定数となり得るのは数値、文字列、論理値だけです。定数はコンパイル時に作成されるという制約上、コンパイラによって評価可能な定数式でなければなりません。たとえば1<<3
は定数式ですが、math.Sin(math.Pi/4)
は定数式ではありません。これは後者を評価するためにはmath.Sin
の呼び出しが必要となるためです。
Go言語での定数の列挙にはiota
列挙子を使います。iota
は式の一部となって、かつその式は暗黙的に繰り返すことができるので値の複雑なセットも簡単に作成することができます。
type ByteSize float64 const ( _ = iota // 一番目の値はブランク識別子に代入して無視 KB ByteSize = 1<<(10*iota) MB GB TB PB EB ZB YB )
String
のようなメソッドを型と結びつけることができるので、型の一部として上のような値を出力用に自前で自動フォーマットすることが可能になります。
func (b ByteSize) String() string { switch { case b >= YB: return fmt.Sprintf("%.2fYB", b/YB) case b >= ZB: return fmt.Sprintf("%.2fZB", b/ZB) case b >= EB: return fmt.Sprintf("%.2fEB", b/EB) case b >= PB: return fmt.Sprintf("%.2fPB", b/PB) case b >= TB: return fmt.Sprintf("%.2fTB", b/TB) case b >= GB: return fmt.Sprintf("%.2fGB", b/GB) case b >= MB: return fmt.Sprintf("%.2fMB", b/MB) case b >= KB: return fmt.Sprintf("%.2fKB", b/KB) } return fmt.Sprintf("%.2fB", b) }
式YB
の出力は1.00YB
になり、ByteSize(1e13)
の出力は9.09TB
となります。
変数
変数は、定数と同じように初期化することができますが、変数のイニシャライザは通常の式であり、実行時に評価されます。
var ( HOME = os.Getenv("HOME") USER = os.Getenv("USER") GOROOT = os.Getenv("GOROOT") )
init関数
最後になりますが、各ソースファイルにはそれぞれ必要に応じて、セットアップのためにinit()
関数を定義することができます。ひとつだけ制約があり、初期化中にゴルーチンを起動することはできますが、初期化が完了するまで実行は開始しません。すなわち、初期化処理中に実行されるスレッドは常にひとつだけです。
init()
関数は、パッケージでインポートしている他のパッケージが初期化されたあと、パッケージ内で宣言されているすべての変数のイニシャライザが評価されたあとに呼び出されます。init()
関数の一般的な使い方は、宣言としては記述できないような初期化処理を行うほか、処理の実行開始直前に、プログラムのステートの妥当性チェックおよびステートの修正を行います。
func init() { if USER == "" { log.Exit("$USER not set") } if HOME == "" { HOME = "/usr/" + USER } if GOROOT == "" { GOROOT = HOME + "/go" } // GOROOTはコマンドラインから--gorrotフラグを指定することで上書き可能 flag.StringVar(&GOROOT, "goroot", GOROOT, "Go root directory") }
Comments