はじめに
こんにちは、nukkyです。
今回はGo言語の連載2回目です。
普段はアプリエンジニアでiOS時々Androidなのですが、今回RE:ENGINESでGo言語の勉強会を行い、せっかくの機会なので自分の知見を広げるためにも今回のブログはGo言語の基礎、基本文法を紹介していこうと思います。
変数
変数の定義
Goにおける全ての変数には「型」を備えます。「型」の詳細については後述します。
早速、変数を宣言してみます。
1 2 | // int型の変数nを定義します var n int |
予約語である「var」の後に「変数の名前」を指定し、最後に「変数の型」を指定します。「var」を使用する場合は変数の名前と型の両方を明示的に指定して変数を定義する必要があります。
同じ型の変数であれば次のように変数をまとめて定義することも可能です。
1 | var x, y, z int |
次のように「var」以下の内容を()で囲うことで異なる型の変数をまとめて定義することも可能です。変数定義を行うブロックが見やすくなるメリットがあります。
1 2 3 4 | var ( x, y int name string ) |
暗黙的な定義
関数の中では上記「var」の代わりに演算子「:=」を使用した暗黙的な定義ができます。暗黙的な定義は型の指定が必要ないという特徴を持ちます。
1 2 3 4 5 | func main() { // int型iの変数を定義して1を代入しています i := 1 fmt.Println(i) } |
このように、演算子「:=」を使用することで変数の型の定義と値の代入をまとめて行えます。型指定は見当たりませんが上記のように整数1を値として代入すると、変数iの型は暗黙的にint型であると推論されます。このような機能は一般に「型推論」と呼ばれます。
varと暗黙的な定義
「var」を用いた定義でも初期化子を与えることで型を省略することができます。
1 2 3 | // int型iの変数を定義して1を代入しています var i = 1 // i := 1と同じ |
なお、関数の外で変数を定義する場合は、var を使用する必要があります。
ローカル変数とパッケージ変数
Goの変数は定義される場所によって2種類に分かれます。任意の関数の中に定義された変数が「ローカル変数」で、関数定義の外部に定義された変数を「パッケージ変数」になります。ローカル変数と異なりパッケージ変数は文字通りパッケージに所属する変数です。次のコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import ( "fmt" ) /* nはパッケージ変数 */ var n = 100 func main() { /* パッケージ変数nの値を+1して表示 */ n = n + 1 fmt.Printf("n=%d\n", n) } |
パッケージ変数のnは、mainパッケージの中であればどこからでも参照することができます。パッケージ変数はローカル変数とは違い、プログラム全体で一つの値が共有されることに注意してください。
また、後述のスコープで説明しますが、パッケージ変数は、識別子の先頭を大文字にすることで、他のパッケージから参照を可能にすることもできます。名前空間付きのグローバルな変数と同じ扱いになりますので、用法には十分注意をしてください。
定数
const
Goでは予約語「const」を使用して定数の定義が可能です。
1 2 | // 定数Xの値は整数1 const X = 1 |
「var」による変数の定義と同様に「()」で複数の定数をまとめて定義できます。
1 2 3 4 5 | const ( X = 1 Y = 2 Z = 3 ) |
次のコードでは定数YとZの値が省略されてますがコンパイルエラーにはなりません。はじめに定義されている定数Xの値がそのまま以降の定数にも割り当てられます。
1 2 3 4 5 | const ( X = 1 // X == 1 Y // Y == 1 Z // Z == 1 ) |
定数の値を省略しつつ途中で別の値をもつ定数を定義した場合は、その新しい値が以後の定数の暗黙的な値に切り替わります。
1 2 3 4 5 6 7 | const ( X = 1 // X == 1 Y // Y == 1 Z // Z == 1 s1 = "A" // s1 == "A" s2 // s2 == "A" ) |
iota
GoにはCやJavaにおける「列挙型(enum)」のような機能はありません。しかし、定義済み識別子「iota」を定数定義と組み合わせて使用することで、Cにおける列挙型に近い振る舞いを実現できます。
1 2 3 4 5 | const ( A = iota // A == 0 B = iota // B == 1 C = iota // C == 2 ) |
iotaを使用した後で定数の値を省略すると暗黙的にiotaが繰り返されます。
1 2 3 4 5 | const ( A = iota // A == 0 B // B == 1 C // C == 2 ) |
関数
関数定義の基本
2つのint型の値を足し合わせるだけの単純な関数を定義して見ます。
1 2 3 | func plus(x, y int) int { return x + y } |
関数は予約語「func」を使用して定義します。関数定義の書き方にもバリエーションがあるのですが、基本形は次の通りです。
1 2 3 | func [関数名] ( [引数の定義] ) [戻り値型] { [関数の本体] } |
Goでは戻り値を持たない関数を定義することもできます。次のコードのように、単に戻り値の型定義を省略するだけです。
1 2 3 4 | func hello() { fmt.Println("Hello, World!") return } |
複数の戻り値
Goの関数は複数の戻り値を返すことができます。次のコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import ( "fmt" ) func div(a, b int) (int, int) { q := a / b r := a % b return q, r } func main() { q, r := div(19, 7) fmt.Printf("商=%d 剰余=%d\n", q, r) } |
複数の戻り値を返す関数では、1つ1つの戻り値の型を「(int, int)」のように「()」で囲って、すべて列挙します。なお、次のように「()」を省略すると、コンパイルエラーが発生します。
1 | func div(a, b int) int, int { // コンパイルエラー |
関数が複数の戻り値を返すのであれば、呼び出し元は複数の戻り値を受け取る必要があります。代入先の変数をカンマで区切って並べ、関数が返す複数の戻り値をそれぞれの変数に割り振って代入することができます。
1 | q, r := div(19, 7) |
関数が複数の戻り値を返すからといって、そのすべてに変数に割り当てる必要はありません。次のように、「_」を使って戻り値の一部を破棄することが出来ます。
1 2 3 | q, _ := div(19, 7) _, r := div(19, 7) _, _ := div(19, 7) // コンパイルエラー |
上記の通り全ての戻り値を破棄することは出来ません。
関数の名前付き戻り値
Goでは、戻り値にあらかじめ名前を付けることができます。先ほどの関数の戻り値に、次のように名前を付けてみます。
1 | func div(i, j int) (result int, err error) |
名前付き戻り値は、関数内ではゼロ値で初期化された変数として扱うことができます。また、変数に名前を付けている場合は、returnのあとに返す値を明示する必要がなく、returnされた時点での名前付き戻り値の値が自動的に返されることになります。
これを用いると、先の関数は次のように書くことができます。
1 2 3 4 5 6 7 8 | func div(i, j int) (result int, err error) { if j == 0 { err = errors.New("divied by zero") return // return 0, errと同じ } result = i / j return // return result, nilと同じ } |
名前付き戻り値を用いることで、関数の宣言から戻り値の意味が読み取りやすくなると同時に、戻り値のための変数の初期化が不要になり、同じ型の戻り値が多かった場合のreturnの書き間違えなどを防ぐこともできます。
関数とエラー処理
Goには例外機構がありません。つまり、任意の関数を呼び出した場合に、その処理が成功したかどうかを何らかの形で検知する必要があります。Goでは複数の戻り値を返すことができるという特性を利用して、エラーが発生したかどうかを戻り値の一部で示します。
1 2 3 4 | result, err := doSomething() if (err != nil) { // エラー処理 } |
関数「doSomething」は2つの戻り値を返し、変数「err」で受け取ってる2番目の戻り値はエラー発生の有無を表しています。このような書き方は、Goにおける一種のイディオムであり、頻出する表現です。エラー内容を割り当てる変数名がerrなのも慣例的な決まりごとなので、Goのエラー処理を書く場合は、できるだけこの形式に従うべきでしょう。
無名関数
ここまでは明示的に名前を与えられた関数の定義について確認してきましたが、それとは別にGoには「無名関数」という機能が用意されています。これは関数というものをある種の「値」として表現したものと見なせます。関数を値として表現できるのであれば、ある関数が関数を引数にとることも、関数を返す関数を書くことも自在にできます。
次のコードでは、変数fに「intの引数x,yをとりint型を返す無名関数」を代入しています。変数fは名前付きで定義された通常の関数と同様のもので、「f(2, 3)」のように引数を渡して呼び出すことができます。
1 2 | f := func(x, y int) int { return x + y } f(2, 3) // == 5 |
関数リテラルは次のように記述します。関数名が与えられないところを除けば、名前付き関数の定義方法と同様の書き方です。
1 | func( [引数リスト] ) [戻り値型] { [無名関数の本体] } |
関数リテラルを使用して生成された無名関数はどのような型を持つのでしょうか。先ほどと同じ無名関数を書式指定子「%T」をつかって型を調べて見ます。
1 2 | fmt.Plintf("%T\n", func(x, y int) int { return x + y }) // => "func(int, int) int" |
上記のコードから得られた出力は「func(int, int) int」というものでした。これが「intの引数を2つとりint型の戻り値を返す関数」の型を表します。こちらは型になるので明示的に変数を定義することもできます。ただGoには型推論があるので、わざわざこのような書き方を選ぶメリットはないですが、暗黙的な変数定義によって隠蔽された構造がこのような形式になっていることを理解してください。
1 2 | var f func(int, int) int f = func(x, y int) int { return x + y } |
関数を返す関数
次のようにして、「関数を返す関数」を定義することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import ( "fmt" ) func returnFunc() func() { return func() { fmt.Plintln("I'm a function") } } func main() { f := returnFunc() f() // => "I'm a function" } |
関数returnFuncは、引数をとらず「引数も戻り値もない関数」を戻り値として返す関数として定義されています。
1 | returnFunc()() // => "I'm a function" |
変数を経由せずに、returnFuncの戻り値である関数を、そのまま直接呼び出すこともできます。Goではよくある書き方ですので押さえておきましょう。
スコープ
スコープとは
Goのプログラムコードの任意の場所で、どのような定数や関数が参照可能であるかどうかは、すべて「スコープ」によって決定されます。Goにおけるスコープは大きい単位から「パッケージ」「ファイル」「関数」「ブロック」「制御構文(if、forなど)」によって決定されます。
とくに、定義済み識別子を除いた、関数・変数・定数・型といったプログラムの構成要素はすべてパッケージに属するため、パッケージのスコープを理解することは「要素の可視性」のコントロールのためにも重要です。
パッケージのスコープ
Goのプログラムは複数のパッケージを組み合わせて構成されます。各パッケージ間で定数や関数を共有するために、パッケージ下に定義された識別子を他のパッケージからも参照できるようするのか、内部のみで利用する識別子にして隠蔽するのか、それぞれの識別子をコントロールする必要があります。
パッケージに定義された定数、変数、関数などが他のパッケージから参照可能であるかは、「識別子の1文字目が大文字」であるかどうかで決定されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package foo const ( abc = "abc" // fooパッケージ内のみで参照できる定数 A = 1 // 他のパッケージにも公開される定数 ) var ( m = 256 // fooパッケージ内のみで参照できる変数 N = 512 // 他のパッケージにも公開される変数 ) /* fooパッケージ内のみで参照できる関数 */ func doSomething() { (中略) } /* 他のパッケージにも公開される関数 */ func DoSomething() { (中略) } |
なお識別子1文字目が日本語など大文字、小文字がない文字である場合には小文字で定義したのと同様に他のパッケージから参照できません。
ファイルのスコープ
Goでは1つのパッケージを定義するために複数のGoファイルを使用できます。パッケージ定義が複数のファイルによって構成されている場合、import宣言は各々のファイルのみ有効になります。
同一パッケージであればファイルが分割されていても、定数や関数といった要素に関しては相互に参照可能ですが、import宣言のみ独立していることに注意してください。
関数のスコープ
Goの関数はスコープを構成します。関数の中で定義された変数や定数は、定義された関数の中でのみ参照可能です。
関数のスコープには、引数の変数と戻り値の変数も含まれています。次のように同名の識別子を関数内で改めて定義しようとすると、コンパイルエラーが発生します。
1 2 3 4 | func doSomething(num int) { var num int // 識別子numは定義済み return } |
関数の中には「{}」を使って明示的に別のブロックを定義できます。ブロックを分けることで関数スコープにある識別子と重複する識別子を使用できます。
1 2 3 4 5 6 7 | func doSomething(num int) { { /* 関数より深いブロック */ var num int } return } |
ここで定義されている変数numは「{}」ブロック内でしか参照できないので注意してください。
型
Goの基本型
Goは「静的型付け言語」です。すべての変数は何らかの型に属し、異なる型同士の演算といった問題点の多くはコンパイル時に検出されます。
以下がGoの基本型の一覧になります
型 | 説明 |
---|---|
uint8 | 8ビット符号なし整数 |
uint16 | 16ビット符号なし整数 |
uint32 | 32ビット符号なし整数 |
uint64 | 64ビット符号なし整数 |
int8 | 8ビット符号あり整数 |
int16 | 16ビット符号あり整数 |
int32 | 32ビット符号あり整数 |
int64 | 64ビット符号あり整数 |
float32 | 32ビット浮動小数 |
float64 | 64ビット浮動小数 |
complex64 | 64ビット複素数 |
complex128 | 128ビット複素数 |
byte | uint8のエイリアス |
string | 文字列型 |
rune | Unicodeのコードポイント |
uint | 32か64ビットの符号なし整数 |
int | 32か64ビットの符号あり整数 |
uintptr | ポインタ値用符号なし整数 |
bool | 論理値型 |
error | エラーを表わすインタフェース |
型キャスト
Goでは、暗黙的な型変換が起こることはありません。しかし型を変換できないわけではなく、キャストで明示的に変換することができます。
キャストは、キャストしたい型を指定して次のように行います。
1 2 3 4 | n := 1 // int型 b := byte(n) // byte型へ変換 i64 := int64(n) // int64型へ変換 u32 := uint32(n) // uint32型へ変換 |
Goの型変換は「型(値)」のように書くことができます。
型アサーション
どんな型の値でも受け取れるinterface{}ですが、interface{}型の引数で受け渡された値は、元の型の情報が欠落しています。
(interface{}型については基本構文その2で説明します)
Go言語ではこのような局面で利用するための型アサーションを提供しており、型アサーションにより実体の型が何であるかを動的にチェックすることができます。構文は次のような形になります。
1 | <変数>.(<型>) |
基本的に以下のように記述します、1番目の変数には型アサーション成功時に実際の値が格納されます。2番目の変数には型アサーションの成功の有無(true/false)が格納されます。
1 | value, ok := <変数>.(<型>) |
以下はサンプルの関数です。引数に型アサーションを用いて型に応じた処理の分岐をさせています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | func printIf(src interface{}) { if value, ok := src.(int); ok { fmt.Printf("parameter is integer. [value: %d]\n", value) return } if value, ok := src.(string); ok { value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) return } fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) } |
ぱっと見たところ「value」と「ok」が関数内に2回宣言されているように見えますが、ifに記載することでif文のブロック内でしかスコープされていない状態になりコンパイルエラーは発生しません。(この書き方は後述のifで説明します)
制御構文
for
Goに用意されている制御構文は、他のプログラミング言語と比較すると非常にシンプルに構成されています。たとえば、ループを記述するために用意されているのは「for」のみです。
1 2 3 4 5 6 7 8 9 | /* 条件文を指定しない */ for { /* 無限ループ */ } /* 条件文を指定した書き方 */ for i := 0; i < 10; i++ { /* 変数iの値が0から9までループ */ } |
if
ifは条件文を構成します。他のプログラミング言語と同じようにifの後に条件式を記述することでブロックが実行されます。
1 2 3 | if x == 1 { /* xの値が1である場合に実行されるブロック */ } |
Goではifの書き方にもう一つバリエーションがあります。「簡易文」を伴う条件文です。簡易文とは「式」や「代入文」、「暗黙の変数定義」などの複雑な構造を持たない単一文のことです。簡易文は条件式の前におかれ「;」で区切られます。
1 2 3 | if [簡易文] ; [条件式] { (中略) } |
型アサーションで使用したコードを見てみましょう。
1 2 3 4 | if value, ok := src.(int); ok { fmt.Printf("parameter is integer. [value: %d]\n", value) return } |
これが簡易文付きifになります。簡易文は条件式に先立って評価されます。なので上記のコードだと「value」と「ok」に条件式よりも前に値が入り、論理値「ok」で条件を判断しています。Goではifに類する各制御構文は暗黙的ブロックを構成します。ifの外側で定義された変数と、内側で定義された変数とではスコープが異なります。
switch
if/
1 2 3 4 5 6 7 8 9 10 11 | n := 10 switch n { case 1: fmt.Println("1") case 5, 10: fmt.Println("5 or 10") case 3, 6, 9: fmt.Println("3 or 6 or 9") default: fmt.Println("unknown") } |
Goのswitch文では、caseに値だけでなく式も指定できます。
1 2 3 4 5 6 7 8 9 10 11 | n := 10 switch { case n%15 == 0: fmt.Println("FizzBuzz") case n%5 == 0: fmt.Println("Buzz") case n%3 == 0: fmt.Println("Fizz") default: fmt.Println(n) } |
さらにGoのswitch文では、caseに型も指定できます。型アサーションと分岐を組み合わせた処理を手軽に書くことができます。
1 2 3 4 5 6 7 8 | func Print(value interface{}) { switch v := value.(type) { case string: fmt.Printf("value is string: %s\n", v) case int: fmt.Printf("value is int: %d\n", v) } } |
おまけ:goto
Goでは関数内の任意の位置にジャンプするための「goto」文が用意されています。gotoは使いどころが難しく基本構文その1ではそんなのもあるよ程度に留めておきます。
さいごに
まずは基本構文その1としてGo言語の基礎、基本文法を紹介させていただきました。せっかくの連載なのと自分の熱が高いうちにということで基本構文その2は明日公開します!
Go記事の連載などは、こちらをご覧ください。