基礎からしっかり学ぶGo言語 2

Goの基本的な文法を学ぼう

2つの簡単なプログラムの作成を通じて、Go言語の特徴的な文法を学んでいく。

松尾 将幸, [監修]上田 拓也

2016年11月29日 12:01

(編注:2017年7月21日15時20分更新)記事公開当初の内容に誤植と技術的な誤りがありました、お詫びして訂正致します(解説を大幅に追加しました)。今回の修正と第3回以降の記事では外部監修者によるチェック体制を強化しております。

はじめに

前回は、Go言語の概要紹介とHello, Worldの実行までを行いました。今回はさらにGoの魅力を理解してもらえるように、簡単なプログラムを2つ作成し、それを通して型やインターフェースといったGoの文法を紹介していきたいと思います。なお、以下ではLinuxもしくはMacで作業することを前提として書いていますが、WindowsでもコマンドプロンプトやPowerShellで同じようにできますので、ご安心ください。

Webサーバを作ってみよう!

まずはGoの威力を知るため、簡単なWebサーバを作ってみましょう。「server.go」というテキストファイルを任意の場所に作成し、以下のようにエディタで書いてみてください。

リスト1:Goで作る簡単なWebサーバ

package main

import (
    "fmt"
    "net/http"
)

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
    var text string
    text = "Hello, World"
    w.Write([]byte(text))
}

func main() {
    http.HandleFunc("/", ServeHTTP)
    fmt.Println("Web Server Start!")
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        fmt.Println("Error!")
    }
}

前回は「go run」コマンドを使ってプログラムを実行しましたが、今回は「go build」コマンドを使ってバイナリを作ってみましょう。

リスト2:go buildコマンドの実行例

$ go build -o server server.go

「go build」コマンドの実行後に、何も表示されなければ実行は成功です。これで、「go build」コマンドを実行した環境に最適化されたバイナリファイルが生成されます。なお、失敗した場合、例えば以下のようにエラーメッセージが表示されます。エラーが発生したファイル名と行数、エラーメッセージが表示されますので、それを見てエラーを修正し、再度「go build」を実行してみましょう。

リスト3:関数名を間違えた場合のエラーメッセージ例

$ go build -o server server.go
# command-line-arguments
./server.go:15: undefined: ServeHTTP

成功していれば、同じディレクトリに「server」という名前のバイナリファイルが作成されているはずです。早速、それをターミナル上で実行してみます。

リスト4:作成したWebサーバを実行

$ ./server
Web Server Start!

ここでブラウザから「http://localhost:8080」にアクセスしてみましょう。以下のように表示されていれば成功です。

Goで作成したWebサーバが起動している

Goで作成したWebサーバが起動している

このようにGoを使えば、わずか20行にも満たない行数でWebサーバを作って動かすことができます。なお、「server」を停止する場合は、ターミナル上で「Ctrl」キーと「C」を同時に押して停止してください。

プログラムの内容を理解しよう!

それでは今作成したプログラムを解説していきます。

3.1 パッケージ

リスト5:package

package main

これは「server.go」ファイルが所属する「パッケージ」を宣言しています。Goでは、全てのプログラムは何かしらの「パッケージ」に所属している必要があります。また、Goのプログラムが起動した直後に「main」パッケージに属する「main関数」が実行されます(実際には、それより先に各パッケージの「init関数」が実行されます)。もし「mainパッケージ」がなかったり、「main関数」がなかったりしたらどうなるのでしょうか?

リスト6:例:mainパッケージがない場合

$ go run server.go
go run: cannot run non-main package

リスト7:例:main関数がない場合

$ go run server.go
# command-line-arguments
runtime.main: call to external function main.main
runtime.main: main.main: not defined
runtime.main: undefined: main.main

このように、「mainパッケージ」や「main関数」がなければ、実行時にエラーとなってしまいます。そのため、必ず書くようにしましょう。

インポート

リスト8:import

import (
    "fmt"
    "net/http"
)

この部分では「server.go」が使用する他のパッケージを宣言しています。「mainパッケージ」の中に書かれていない関数を使う場合、その関数を持つパッケージ名を記載しなければなりません。今回は「fmtパッケージ」と「net/httpパッケージ」に含まれている関数を使っているため、この2つのパッケージを宣言する必要があります。

関数

リスト9:関数

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
    var text string
    text = "Hello, World"
    w.Write([]byte(text))
}

ここでは「ServeHTTP関数」を定義し、その処理内容を記述しています。関数は基本的に以下の書式で書き表します。角カッコの部分は、必要ない場合は省略可能です。

リスト10:関数の書式

func 関数名 ([引数1, 引数2,…]) [返り値の型] { 
    // 処理内容
    // 返り値がある場合、「return 返り値1, 返り値2,…」を記述すること。
}

関数を使う場合、同じパッケージであれば「関数名([引数])」のように記述し、他のパッケージの場合は、「パッケージ名.関数名([引数])」と書きます。

また、返り値は複数返すことが可能です。複数返す場合は、「(返り値の型1, 返り値の型2,…)」のように丸括弧で囲む必要があります。 さらに、返り値は、引数と同じように識別子をつけることができます。その場合、以下のような書き方が可能となります。

func GetNumber() (n int) {
    n = 10
    return  // return n や return 10と書いても問題ありません。
}

int型の返り値にあらかじめ「n」という識別子を付けておき、関数内で10を代入してreturnしています。この時「return n」と書く必要はありません。単にreturnと書くだけでnを返してくれます。

なお、Goで頻出するイディオムとして、以下のように1つ目の返り値が関数の結果、2つ目の返り値はエラーが発生したかどうかを表す、というコードがよく登場します。

リスト11:よく使われる返り値の書き方

result, err := something()

さらに返り値を受ける時、「 _(アンダースコア)」を使用すると、変数を定義せずに返り値を無視することが可能です。以下のように書けば、関数の中でエラーが発生したかどうかだけを受け取れます。

リスト12:エラー発生の有無だけを受け取る書き方

_, err := something()

変数と型

Goでは、変数を以下のように宣言します。

リスト13:変数宣言の書式

var 変数名 型名

「ServeHTTP関数」では以下のように1行目で「string型」の変数「text」を宣言し、2行目で文字列を代入しています。

リスト14:ServeHTTP関数での変数宣言の例

var text string
text = "Hello, World"

Goには、変数の型として、以下のような多種多様な型が用意されています。

種別型名説明
整数型int32bit、もしくは64bitの整数を表す
int88bitの整数を表す
int1616bitの整数を表す
int3232bitの整数を表す
int6464bitの整数を表す
uint32bit、もしくは64bitの符号なし整数を表す
uint8(別名byte)8bitの符号なし整数を表す
uint1616bitの符号なし整数を表す
uint3232bitの符号なし整数を表す
uint6464bitの符号なし整数を表す
浮動小数点数型float3232bitの浮動小数点数(小数)を表す
float6464bitの浮動小数点数(小数)を表す
複素数型complex6464bitの複素数を表す(実部32bit、虚数部32bit)
complex128128bitの複素数を表す(実部64bit、虚数部64bit)
文字型string文字列を表す
runeint32と同型。Unicodeのコードポイントを表すために使用
真偽値booltrue、もしくはfalseを表す
配列[要素数]型名要素数で指定した数の要素を持つリストを実現する。
スライス[]型名可変長の要素を持つリストを実現する
マップmap[型名]型名2つの任意の型(関数型とスライス、マップ、チャネルを除く)を紐付けるkey/value形式を表す
チャネルchan 型名複数のゴルーチンの間でデータを受け渡しするときに使用する
関数型func関数を表す。関数を変数に代入できる
構造体struct{ フィールドの一覧 }複数のデータを1つのまとまりとして表す
インターフェースinterface{ メソッドの一覧 }実装すべきメソッドを定義した型(詳細は後述)。
ポインタ*(型名)値が格納されているメモリへのアドレスを表す

Goは型システムは非常に厳密であり、異なる型の変数同士の直接代入はできず、他の言語のように「暗黙の型変換」もありません。そのため、以下のような代入はエラーになります。

リスト15:Goでエラーとなる代入の例

var a int
var b float
b = 10.0
a = b  // エラー

また整数型の「int」は、インストールしたGoのbit数によって「32bit」か「64bit」のどちらかになります(32bit版のGoをインストールすれば「int32」と同じになります)。ただし、どちらであっても、「int32」や「int64」との互換性はありません。よって、以下の代入は失敗します。

リスト16:Goでエラーとなる代入の例intとint64

var a int   // 64bit版のGoとする
var b int64
b = 100
a = b  // エラー

もし異なる型の変数に値を入れたいのであれば、「キャスト(明示的な型変換)」が必要となります。

リスト17:キャスト

var a int
var b float
b = 10.0
a = int(b)  // 成功、aに10が入る

このように厳密な型システムを持つことで、ビルド時にプログラムのミスをより多く見つけることが可能になります。さらにGoの素晴らしいところは、型の恩恵を享受しつつプログラムの記述量を減らせる「型推論」の仕組みを持つところです。

リスト18:変数宣言の例

var text string
text = "Hello, World"

例えば上の2行の例は、以下のように1行で書くことができます。

リスト19:型推論を利用して記述方法

text := "Hello, World"
ここでは「var」も「string」も書いていませんが、「text」という新しい変数を宣言したうえで、それを「"Hello, World"」という文字列で初期化しています。まず、「=」が「:=」となっている点に注目してください。これは「新しく変数を定義する」という意味になります。その上で、Goのコンパイラは、「"Hello, World"という文字列を入れているので、このtextという変数はstring型だ」と判断し、プログラマが何も書かなくてもtext変数をstring型として扱ってくれます。よって、以下のコードはエラーと判定されます。

リスト20:型推論と異なる型への代入はエラー

text := "Hello, World"
text = 100  // エラー

この言語仕様のため、Goを使うプログラマが型に悩まされることはあまり多くありません。さらにGoのコンパイラは、未使用の変数があった場合もエラーとしてくれるなど、コードの可読性に徹底的に気を配った仕様となっています。

配列

値型の変数に関しては、配列とすることができます。配列の書式は以下のようになります。

リスト21:配列の書式

var 変数名 [要素数]型名           // 宣言のみ:この[]は省略可能という意味ではありません
変数名 := [要素数]型名{値1,値2,…}  // 初期化まで行う場合

また、例えばint型配列の初期化を行う場合は,以下のように書きます。

リスト22:int型配列の初期化

numbers := [3]int{1,2,3}

要素数も「型」の一部であることに注意してください。そのため、「[3]int」と「[5]int」は全く別の型として扱われます。

なお、Goにおいては、配列よりもスライスがよく使われます。スライスであれば、配列のように要素数が固定されないためです。

var 変数名 []型名

条件分岐

Goではif文は以下のように書きます。

リスト23:if文の書式

if 分岐条件 {
    // 処理内容
} else if 分岐条件 {
    // 処理内容
} else {
    // 処理内容
}

分岐条件を丸括弧で囲わないところが特徴的です。また、以下のように分岐条件の前に代入文を分けて書くこともできます。

if err := foo() ; err != nil {
  // エラー処理
}

変数への代入と、それを使った判定を一度に書けるのは他の言語でもできますが、文の終端を意味するセミコロンを間に挟んで、2つの式をまとめて書くことができるというのはGoならではでしょう。また、例えば同値判定をする場合は以下のような書き方をします。

リスト24:同値判定の書き方

if i == 0 {
    fmt.Println("処理成功")
}

main関数

前述のとおり、Goのプログラムには必ず「main関数」が必要です。「server.go」の「main関数」は以下のようになっています。

リスト25:server.goのmain関数

func main() {
    http.HandleFunc("/", ServeHTTP)
    fmt.Println("Web Server Start!")
    err := http.ListenAndServe("localhost:8080", nil)
    if err != nil {
        fmt.Println("Error!")
    }
}

最初の「http.HandleFunc」は、「httpパッケージ」に属する「HandleFunc」を呼び出しています。「HandleFunc」の引数として、「"/"」と「ServeHTTP(関数)」を渡しています。

これはブラウザから「Webサーバのドキュメントルート」にアクセスされた際に、自動的に「ServeHTTP」が呼び出されるように登録をしています。その次の「fmt.Println」は、バイナリファイルを実行した時に、コマンドラインに「Web Server Start!」と表示するためのものです。

最後の「http.ListenAndServe」は「ホスト名」「localhost」、「ポート番号」「8080」でブラウザからのアクセスを待ち受けるWebサーバを起動することを表しています。

Webサーバとして起動するために必要なコードは、1行目と3行目の2行だけです。Goが簡単に高度なプログラムを書ける言語だということを理解してもらえたのではないでしょうか。

この記事をシェアしてください

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored