Go (プログラミング言語)

Goはプログラミング言語の1つである。Googleにおいて2009年ロバート・グリースマ、ロブ・パイク、ケン・トンプソンによって設計された。Goは、静的型付け、C言語の伝統に則ったコンパイル言語、メモリ安全性、ガベージコレクション、、CSPスタイルの並行性などの特徴を持つ。Goのコンパイラ、ツール、およびソースコードは、すべて自由かつオープンソースである。

また、軽量スレッディングのための機能、Pythonのような動的型付け言語のようなプログラミングの容易性、などの特徴もある。Go処理系としてはコンパイラのみが開発されている。マスコット・キャラクターはGopher（ホリネズミ）。

発表当初はLinuxとMac OS Xのみしかサポートしていなかったが、2012年3月にリリースされたバージョン1.0からはWindowsもサポートされている。2014年12月にリリースされたバージョン1.4からAndroidをサポートし、2015年8月19日にリリースされたバージョン1.5からiOSをサポートしている。また、2011年5月10日に公開された Google App Engine 1.5.0でも、Go言語がサポートされている。2018年8月にリリースされたバージョン1.11からWebAssemblyをサポートした。

 名称 
しばしばGolangやGo言語と表記されることがあるが、言語の正式名称はGoである。golangという通称は、公式サイトのドメインであるgolang.orgに由来するものである。このドメインになったのはgo.orgが取得できなかったからであるとされている。

また、公式のロゴではすべて大文字でGOと描かれているが、言語の名前としてはGoであるとされている。

歴史

2009年11月10日にGoが初めて発表され、バージョン1.0が2012年3月28日にリリースされた。GoはGoogleの本番システムの一部で使用されており、他の多数の企業やオープンソースプロジェクトでも使用されている。

主に次の2つの実装が存在する。

GoogleのGoツールチェイン。複数のプラットフォームをサポートとしており、Linux、BSD、macOS、Plan 9、Windows、そして（2015年から）モバイルデバイスで動作する。このGoコンパイラは、バージョン1.5からセルフホスティングされるようになった。
2つ目のコンパイラgccgo。これは、GCCのフロントエンドである。

Goの起源はGoogleのエンジニアであるRobert Griesemer、ロブ・パイク、ケン・トンプソンによる、新しいプログラミング言語を設計する実験的なプロジェクトに端を発する。Googleなど、多くのIT企業ではソースコードの複雑さや肥大化が問題になっていた。他の言語がよく受ける批判を解決するとともに、それらの言語のよい特徴をできる限り損なわないようにすることを目指した。彼ら設計者たちは、新しい言語として、以下の特徴を持つものを構想していた。

 JavaやC++のように、静的に型付けされ、巨大なシステムでもスケールする
 RubyやPythonなどの動的な言語のようにが高く、リーダブルであり、過度なボイラープレートが必要ない
 IDEが必須ではない。ただし、十分にサポートする
 ネットワークおよびマルチプロセッシングをサポートする

後のインタビューで、3人の言語設計者すべてが、新しい言語を設計する主なモチベーションとしてを共有していたことを述べている。

バージョン1.6から、64ビットMIPS上で動作するLinux版および32ビットx86上で動作するAndroid版の実験的なポート（移植）が追加された。

2017年7月13日、バージョン2への取り組みへの開始が発表された。バージョン1.x系とバージョン2の開発は並行して行われ、バージョン1.20がバージョン2と同義となると宣言されている。

2018年4月、旧ロゴ（Gopherマスコット）がテキストと3本の直線からなる新しいデザインのものに変更された。しかし、マスコットはそのまま変わっていない。

2021年8月16日、バージョン1.17がリリースされる。64ビットのARMアーキテクチャー用のWindowsがサポートされる。バージョン2系は、Nightly Build系であり、公式にはリリースされていない。これは、プログラム・アーキテクチャーの変更がユーザーに多大な負担を与える事に配慮しているためである（Rustからの反省）。

コード例

 Hello world 
次のコードはGoで書いたHello worldプログラムである。package main

import "fmt"

func main() {
 fmt.Println("Hello, World")
}

 並行性の例 
次のサンプルプログラムは、Goの並行性機能をデモンストレーションする非同期プログラムの実装である。2つの"goroutine"（軽量スレッド）を立ち上げている。一方はユーザーがテキストを入力するのを待機し、他方はタイムアウトを実現する。文が2つのgoroutine がメインルーチンにメッセージを送信するのを待機し、最初に到達したメッセージに対して動作を実行する（コード例はDavid Chisnallの本のコードを一部修正している）。package main

import (
 "fmt"
 "time"
)

func readword(ch chan string) {
 fmt.Println("Type a word, then hit Enter.")
 var word string
 fmt.Scanf("%s", &word)
 ch <- word
}

func timeout(t chan bool) {
 time.Sleep(5 * time.Second)
 t <- true
}

func main() {
 t := make(chan bool)
 go timeout(t)

 ch := make(chan string)
 go readword(ch)

 select {
 case word := <-ch:
 fmt.Println("Received", word)
 case <-t:
 fmt.Println("Timeout.")
 }
}

 変数の宣言と利用、型変換 
次のコードはGoで書いた変数を宣言し実行するプログラムである。前記のコードにて、:=を利用した宣言が行われているが、その場合とは別の手段を使い表記する。testというstring型の変数とafterというstring型の変数とnumというint型の変数を宣言し、numstrという空のstring型の変数を使用し、numをstringに変換してnumstrに値を入れて、testとnumとafterを結合させてtextというstring型の変数に値を入れてコンソールに表示するものである。package main

import (
 "fmt"
 "strconv"
)

func main() {
 var test string = "Today is December "
 var after string = " th"
 var num int = 28
 
 var numstr string
 
 numstr = strconv.Itoa(num)
 
 var text string = test + numstr + after
 
 fmt.Println(text)
}

 言語機能の特徴 

文法
構文は様々な言語に部分的に類似している。変数などにおける型の記法はLimboから引き継いだものと思われる、型名を後置するもので、PascalやAdaに類似している。ブロックの区切りに波括弧を使う記法はC言語他多くと同様である。for文やif文では、条件式を丸括弧で括らず、帰結部分には波括弧が必須である。定数生成器iotaはASP由来である。他に、並列処理について、CSPを参考としている、チャネルによるスレッド間通信機能の構文がある。

構造体
Goにはユーザー定義の複合型を記述することのできる構造体の機能があり、下記のように記述する。
package main

import "fmt"

// 新しい構造体型の定義。各フィールドの識別子とその型名を指定。
type Person struct {
 name string // 名前。
 age int // 年齢。
}

// メソッドの定義。
func (m *Person) PrintName() {
 fmt.Printf("Name = %s\n", m.name)
 //fmt.Printf("Name (%T) = %#v\n", m.name, m.name)
}

func (m *Person) PrintAge() {
 fmt.Printf("Age = %d\n", m.age)
 //fmt.Printf("Age (%T) = %#v\n", m.age, m.age)
}

func main() {
 var person Person
 person.name = "John Smith"
 person.age = 24
 // 以下のように書くこともできる。
 //person := Person{ "John Smith", 24 }
 person.PrintName()
 person.PrintAge()
 // 以下のように書式指定すると、型名とリテラル表現を出力できる。
 fmt.Printf("person (%T) = %#v\n", person, person)
}

上記の例では、nameおよびageというフィールドを持つPerson構造体の型を定義し、さらにその構造体に対応するPrintName()およびPrintAge()というメソッドを定義している。GoにはJavaのような言語が持つクラスやオブジェクトのような概念は無いが、実質的には構造体はそれらに近い役割を提供する。

Goでは、関数に(m *Person)のようなレシーバーの宣言を追加してメソッドを定義する。レシーバーの宣言は (変数名 型名) と記述する。レシーバー内の変数名は自由に指定が可能であり、受け取り方は値渡し、ポインター渡しの2種類から指定できる。

型とメソッドが切り離されており、型は一切メソッドに依存しない。メソッドと型は、メソッドの追加対象となる型名をレシーバーの型名に指定することで関連付けるようになっている。メソッドは型から切り離されているため型本体を変更する事なくメソッドを追加することが可能であり、同一パッケージ内限定ではあるが、開放された型を実現している。

Goには型自身を初期化したり終了したりする機能が存在しない。つまりJavaの静的イニシャライザやC#の静的コンストラクタに相当する機能がない。初期化は型に所属しない関数や、他の型のメソッドで行うようになっている。

新しい型の定義
Goではtype構文を利用して既存の型から新しい型を定義できる。

次の例では、int型を元に MathInt という新しい型を定義している。
type MathInt int
これはC/C++におけるtypedefやC#におけるusingエイリアスディレクティブと違い、単に既存の型に別名を与えているのではなく、元のint型と別の型を定義している点が大きく異なる。

新しく定義した型であれば、元の型が構造体型でなくともメソッドを追加することができる。例えばint型やマップ型といった事前定義済み型 (組み込み型) に対してもメソッドを追加することが可能である。

事前定義済み型から新しい型を定義した場合、例えば下記のような記述が可能となる。
package main

import (
 "fmt"
 "math"
)

type MathInt int

func (x MathInt) Abs() float64 {
 return math.Abs(float64(x))
}

func main() {
	var x MathInt = -10
	fmt.Printf("%d\n", x) // -> -10
	fmt.Printf("%f\n", x.Abs()) // -> 10.000000
	fmt.Printf("%T\n", x) // -> main.MathInt
}

Go 1.9ではtypeキーワードを利用した型エイリアス (type alias) 構文が別途追加された。こちらはC/C++のtypedefやC#のusingエイリアスディレクティブと類似の機能である。

package main

import (
 "fmt"
 "math"
)

type MyInt = int

func main() {
 var x MyInt = -10
	fmt.Printf("%d\n", x) // -> -10
 fmt.Printf("%f\n", math.Abs(float64(x))) // -> 10.000000
	fmt.Printf("%T\n", x) // -> int
}

ジェネリクス
2022年3月にリリースされたGo1.18においてジェネリクスが導入された。ジェネリクスは、型引数を使用して関数やデータ構造に対して任意の型を指定することを可能にする。以下は、基本的なジェネリック関数の例である。

package main

import "fmt"

// 任意の型のスライスを受け取り、その内容を返すジェネリックな関数
func Gather[T any](slice []T) []T {
 result := make([]T, len(slice))
 copy(result, slice)
 return result
}

func main() {
 intSlice := []int{1, 2, 3, 4}
 stringSlice := []string{"Go", "Rust", "Python"}

 fmt.Println(Gather(intSlice))
 fmt.Println(Gather(stringSlice))
}

この例では、Gatherという関数が型引数Tを受け入れ、指定された任意の型のスライスをコピーして返す。これにより、複数の型に対して共通の処理を行うことができ、コードの重複を減らすことができる。

匿名フィールド
Goには継承が意図的に実装されていない。しかし匿名フィールドという機能を用いることで、ある構造体と同じフィールド、メソッドを持つ構造体を容易に作成することができる。

type Base int

func (_ *Base) Function() {
}

type Derived struct {
 Base
}

// 呼び出し例
var derived Derived
derived.Function()

このようにして作られた構造体から匿名フィールドで用いられた元となる構造体の型に暗黙的にキャストするような機能は存在していない。

interface
GoにはC++や他の言語における仮想関数を持ったクラスが存在しない。型に追加したメソッドは一種の非仮想関数である。Goにおいての多態はinterface型を使用して実現する。

interface型は実装を一切持たずメソッドの形式だけを定義した型であり、その点はC#など最近の言語のinterfaceと同じである。但し、Goのinterface型は、代入できる型との関連付けが不要である。interfaceに定義しているメソッドを全て持っていればどんなオブジェクトでも代入可能である。

Goにおけるinterfaceの例を下記に示す。
type Container interface {
	Begin() Iterator
	End() Iterator
}

Goのinterfaceは、MLのsignatureや、かつてg++に存在したsignatureというC++拡張と全く同じ機能である。

defer
ではdefer文という例外安全な強制実行のしくみを採用している。

defer文は、deferキーワードに続けて、関数呼び出しまたはメソッド呼び出しを記述することで関数終了時に指定した処理の呼び出しを実行する。

ファイルのオープンとクローズなど、組になる作業で両方行われる必要があるものに多く使われる。

deferは下記の様に記述する。
defer 関数呼び出しまたはメソッド呼び出し

deferはブロックを持たず入れ子が深くなるようなことはない。ただし、関数以外のブロックを無視する点に注意が必要である。例えばループ中でdeferを使用した場合、ループ内では指定した関数が呼ばれず関数終了時にdeferで指定した全ての関数呼び出しが行われる。

deferには、関数呼び出しまたはメソッド呼び出ししか記述できず、式や文を直接記述することはできないが、下記のように無名関数の即時実行を利用して、任意の式や文の実行も可能である。
defer func() {
	message := "done"
	fmt.Println(message)
}()

例外処理
他の言語で使われている方式の例外処理機能はない。Goにも例外があり、リカバーと合わせてtry-catchのような事もできるが推奨されない。例外機能は、主に0除算など起きてはならないことに使われ普通は強制終了するが、リカバーによって強制終了しないようにする事も可能である。Goでは、事前に例外が起きないよう確認する事や、成功かどうかのBool値を返す事のような、例外を起こさない方法が好まれる。

ダウンキャスト
はインターフェース型の値から、基底型の値（インターフェース型に変換される前の型の値）を動的に安全に得るダウンキャストの仕組みとして「型アサーション」と呼ばれる機能を備えている。

// Value の返値はどんな型でも構わない
var i interface{} = Value()

/*
 i の基底型を string と仮定し、
 実際にそうであれば s には変換結果が、 ok には true がセットされ、
 そうでなければ s にはstring型の初期値（ゼロ値）が、 ok には false がセットされる。

 以下の様に2つ目の返値を無視すると、i の基底型が string でなければ暗黙的にランタイムパニックとなる
 s := i.(string)
*/
if s, ok := i.(string); ok {
 fmt.Println ("文字列: ", s)

} else {
 var kind string

 // 型switch
 switch i.(type) {
 case int:
 kind = "数値"
 default:
 kind = "その他"
 }

 fmt.Printf ("%s: %v\n", kind, i)
}

標準パッケージのreflectを使うことで、対象の値型やその値などの詳細を得ることができる。

名前とモジュールの管理
には名前とモジュールを管理するために、パッケージという仕組みを持っている。

基本的なパッケージの使用例を下記に示す。
package main // 現在のソースファイルが所属するパッケージ名の指定
 // 本記事の例では省略されている事が多いが本来は必須である
 
import _ "./sub/a" // ソースファイル中から名前が使用されないパッケージの取り込み
import . "./sub/b" // パッケージ名を省略して、パッケージ内の名前を現在のソースファイルに全て取り込む
import alias_c "./sub/c"	// パッケージ名に別名を付けてパッケージ名を現在のソースファイルに取り込む
import "./sub/d" // パッケージ名に末端のパッケージ名と同じ名前を付けて現在のソースファイルに取り込む

func main() {
	Function() // のパッケージに所属するFunctionの呼び出し
	alias_c.Function()	// のパッケージに所属するFunctionの呼び出し
	d.Function() // のパッケージに所属するFunctionの呼び出し
}

には多彩なimport文が存在するが、その構成要素は、別名の指定とのソースを格納したフォルダーの指定（またはパッケージバイナリー）という非常に単純なものである。では名前管理とモジュールの取り込み指定を一つの構文にまとめる事で、他の言語では煩わしいモジュールの結合を簡潔なものとしている。

の名前管理の特筆する点として名前解決の方法として階層型の名前空間ではなく別名の仕組（のみ）を採用している点が挙げられる。名前解決をするために長い名前を記述する煩わしさはない。

更ににはモジュール管理の点でも特筆すべき点がある。はソースファイル中に一つ初期化関数（init）を定義する事で、初期化関数により実行時に広域変数を初期化できるという機能を持っている。この初期化関数が呼ばれるソースファイルは、main関数の定義されたソースファイルから連鎖的にimportされたパッケージに含まれるソースファイルだけである。この特性により使う予定もないのに初期化コードが実行されるおそれはない。また、パッケージが初めて参照されるまで初期化が遅延されてしまうという事もない。

アクセス指定子
 には、型レベルのアクセス指定子が存在しないが、パッケージレベルでのアクセス制御が存在する。パッケージレベルでのアクセス制限はシンボル名（変数名、メソッド名、関数など）の先頭一文字目が大文字か小文字かで決定する。大文字であればパッケージに対し公開され、小文字であれば非公開となる。

提供しない機能
型の継承、アサーション、オーバーロードといった機能をあえて提供していない。FAQにおいてオーバーロードは効率的見地から排除されたことが述べられている。関数は多値を返すことができるので、それによりエラーの報告は容易である、としている。

 処理系 
Go言語の処理系は2種類ある。

gcはGo言語で実装されたセルフホスティングのGo言語コンパイラである。バージョン1.4以前はC言語で実装されていたが、バージョン1.5でC言語からGo言語への変換ツールでコンパイラの実装言語を切り替えて、以降はGo言語で開発している。構文解析にyacc・bisonを使用している。

Gccgoは、再帰下降パーサを持つC++フロントエンド、バックエンドに標準GCCを利用したサードパーティー開発のGo言語コンパイラである。