Goはプログラミング言語の1つである。Googleにおいて2009年ロバート・グリースマ、ロブ・パイク、ケン・トンプソンによって設計された。Goは、静的型付け、C言語の伝統に則ったコンパイル言語、メモリ安全性、ガベージコレクション、、CSPスタイルの並行性などの特徴を持つ。Goのコンパイラ、ツール、およびソースコードは、すべて自由かつオープンソースであるgo.dev。
また、軽量スレッディングのための機能、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であるFrequently Asked Questions (FAQ) - The Go Programming Language。golangという通称は、公式サイトのドメインであるgolang.orgに由来するものである。このドメインになったのはgo.orgが取得できなかったからであるとされている。
また、公式のロゴではすべて大文字でGOと描かれているが、言語の名前としてはGoであるとされている。golangという呼び方が定着したのはgoやgo languageだけではWebやSNS上で情報が紛れやすいためで、公式も検索、識別において非常に有用であることを認めている。
歴史 代替文=GoのマスコットGopher|サムネイル|現在のマスコットGopher。以前は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企業ではソースコードの複雑さや肥大化が問題になっていた。他の言語がよく受ける批判を解決するとともに、それらの言語のよい特徴をできる限り損なわないようにすることを目指した。彼ら設計者たちは、新しい言語として、以下の特徴を持つものを構想していた Video available.。
JavaやC++のように、静的に型付けされ、巨大なシステムでもスケールする RubyやPythonなどの動的な言語のようにが高く、リーダブルであり、過度なボイラープレートが必要ない IDEが必須ではない。ただし、十分にサポートする ネットワークおよびマルチプロセッシングをサポートする
後のインタビューで、3人の言語設計者すべてが、新しい言語を設計する主なモチベーションとしてを共有していたことを述べている 。
バージョン1.6から、64ビットMIPS上で動作するLinux版および32ビットx86上で動作するAndroid版の実験的なポート(移植)が追加されたGo 1.6 Release Notes - The Go Programming Language。
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を参考としているhttps://go.dev/doc/faq 、チャネルによるスレッド間通信機能の構文がある。
構造体 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) 構文が別途追加されたGo 1.9 Release Notes - The Go Programming Language。こちらは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) }()
例外処理 関数は多値を返すことが可能であるため、関数はしばしば戻り値としてerror型を返し、呼び出し側でこれがnilであるかを明示的に判定するスタイルの例外処理がしばしば行なわれる。package main
import ( "errors" "fmt" )
func mayFail(fail bool) (string, error) { if fail { return "", errors.New("something went wrong")
return "success", nil }
func main() { result, err := mayFail(true) if err != nil { fmt.Println("Error:", err) return
fmt.Println("Result:", result) }また、他の言語でよくあるようなtry-catch型のエラー処理機構としてpanicおよびrecoverがある。panicはプログラムの実行を即時中断し、スタックトレースを巻き戻す。recoverはdefer内から呼び出すことでパニック状態を捕捉し、プログラムのクラッシュを防ぐことができる。package main
import "fmt"
func safeExecute(f func()) { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r)
}() f() }
func mayPanic() { panic("unexpected error") }
func main() { safeExecute(mayPanic) fmt.Println("Program continues after recovery.") }この二つのエラー処理体系の使い分けについて、Effective Goでは一般的な方法はerrorを返すことであり、プログラムをどうしても停止しなければならないようなエラーのみがpanicを使うべきで、ライブラリなどにおいてもできる限りpanicは使うべきではない、と解説している。
errorを検証し忘れれば当然プログラムの動作に問題が生じ、エラーの検証を適切に行なわなかった結果、Webアプリケーションにおいて認証がまったく機能しないという脆弱性が発生した事例が知られている。
ダウンキャスト はインターフェース型の値から、基底型の値(インターフェース型に変換される前の型の値)を動的に安全に得るダウンキャストの仕組みとして「型アサーション」と呼ばれる機能を備えている。
// 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" // (1)ソースファイル中から名前が使用されないパッケージの取り込み import . "./sub/b" // (2)パッケージ名を省略して、パッケージ内の名前を現在のソースファイルに全て取り込む import alias_c "./sub/c" // (3)パッケージ名に別名を付けてパッケージ名を現在のソースファイルに取り込む import "./sub/d" // (4)パッケージ名に末端のパッケージ名と同じ名前を付けて現在のソースファイルに取り込む
func main() { Function() // (2)のパッケージに所属するFunctionの呼び出し alias_c.Function() // (3)のパッケージに所属するFunctionの呼び出し d.Function() // (4)のパッケージに所属するFunctionの呼び出し }
には多彩なimport文が存在するが、その構成要素は、別名の指定とのソースを格納したフォルダーの指定(またはパッケージバイナリー)という非常に単純なものである。では名前管理とモジュールの取り込み指定を一つの構文にまとめる事で、他の言語では煩わしいモジュールの結合を簡潔なものとしている。
の名前管理の特筆する点として名前解決の方法として階層型の名前空間ではなく別名の仕組(のみ)を採用している点が挙げられる。名前解決をするために長い名前を記述する煩わしさはない。
更ににはモジュール管理の点でも特筆すべき点がある。はソースファイル中に一つ初期化関数(init)を定義する事で、初期化関数により実行時に広域変数を初期化できるという機能を持っている。この初期化関数が呼ばれるソースファイルは、main関数の定義されたソースファイルから連鎖的にimportされたパッケージに含まれるソースファイルだけである。この特性により使う予定もないのに初期化コードが実行されるおそれはない。また、パッケージが初めて参照されるまで初期化が遅延されてしまうという事もない。
アクセス指定子 には、型レベルのアクセス指定子が存在しないが、パッケージレベルでのアクセス制御が存在する。パッケージレベルでのアクセス制限はシンボル名(変数名、メソッド名、関数など)の先頭一文字目が大文字か小文字かで決定する。大文字であればパッケージに対し公開され、小文字であれば非公開となる。
提供しない機能 型の継承、アサーション、オーバーロードといった機能をあえて提供していない。FAQにおいてオーバーロードは効率的見地から排除されたことが述べられている。
処理系 Go言語の処理系は2種類ある§Implementation | Frequently Asked Questions (FAQ) - The Go Programming Language。
gcはGo言語で実装されたセルフホスティングのGo言語コンパイラである。バージョン1.4以前はC言語で実装されていたが、バージョン1.5でC言語からGo言語への変換ツールでコンパイラの実装言語を切り替えて、以降はGo言語で開発している。構文解析にyacc・bisonを使用している。
GccgoはC++で実装された再帰下降パーサを持つフロントエンド、バックエンドに標準GCCを利用したサードパーティー開発のGo言語コンパイラである。
脚注
注釈
出典
関連項目 C言語 プログラミング言語の比較
解説書籍など 松木雅幸、mattn、藤原俊一郎、中島大一、上田拓也、牧大輔、鈴木健太:「改訂2版 みんなのGo言語」、技術評論社、(2019年8月1日)。 Alan A.A.Donovan, Brian W.Kernighan 著、 柴田芳樹 翻訳:「プログラミング言語Go」、丸善出版、(2016年6月20日)。
Goのtourを直接ダウンロードして、オフラインで学習することも可能である。方法としては、まずオフィシャルサイトから最新版のgo-langをダウンロードしてインストールした後、go install golang.org/x/website/tour@latestを実行することで必要最小限のファイルがダウンロードされる。その後、tourを実行することで、オフラインでGoの文法を学ぶことができる。
外部リンク
言語 FAQ
IRC : () メーリングリスト :