つきなみGo

これまでとこれからのGo

Goの歴史

本連載では、筆者(tenntenn)と周囲のGoエンジニアで、Goに関わる今アツい話を取り上げていきます。最新のGoの機能やこれからリリースされるGoの機能はもちろん、実際の開発現場で用いられているノウハウや自作ライブラリやツールについてご紹介します。第1回目はtenntennがこれまでとこれからのGoについて取り上げます。

Goは2009年にGoogleによって公開されたプログラミング言語です。2012年にGo1として正式公開されました。2022年現在では、GoogleやNetflixをはじめとする海外企業だけはなく、筆者の所属するメルカリなど国内の企業でも多く用いられるようになってきました。海外のGoの導入事例は公式サイトのケーススタディで確認できます。国内企業の導入事例はGoの公式Wikiに掲載があります。これらを見るとスタートアップからメガベンチャー、大企業までさまざまなフェーズの企業、また、CtoCからBtoBまで、さまざまな業種・事業で使われていることがわかります。

筆者はGo1リリース前の2011年ごろからGoの学習を始め、10年ほど国内のGoコミュニティの運営に携わってきました。筆者の記憶では、2014年ごろには国内でもプロダクト開発にGoを利用していた企業があり、2016年ごろから導入企業が増えてきた印象があります。2022年現在では、導入企業の多種多様さをみると、Goはプロダクト開発になくてはならない言語になってきていると言っても良いでしょう。

Goが多くの企業で導入されている理由はいったいどこにあるのでしょう。筆者は、次のような理由があると考えています。

  • 開発がしやすい
  • パフォーマンスが良いコードが書きやすい
  • コミュニティの存在

Goがなぜ開発されたのかという情報は、2012年と少し古い記事ですがGoの最初の設計者の1人であるRob Pike氏が書いたGo at Google: Language Design in the Service of Software Engineeringや2022年に公開されたGoチーム(Google社内のGo開発チーム)のRuss Cox氏が書いたThe Go Programming Language and Environmentにあります。

Goが開発された理由は大きくわけて、開発のスケールと(Goで開発した)製品のスケールの2つのスケーラビリティにあるとされています。開発のスケールはビルドの速さ、開発ツールの作りやすさなどがあり、製品のスケールは並行処理による高パフォーマンスなコードの書きやすさなどがあります。筆者は、Goが開発された理由の開発と製品のスケールがGoを利用する企業の課題を解決していったと考えています。

Goは世界各地にコミュニティがあり、コミュニティとGoチームが共に協力しながら発展していきました。たとえば、Goチームが発明したインポートパスとgo getコマンドのアイデアは、誰もがインターネット上に自由にライブラリやツールを公開できるようにしました。Goエンジニアは自分たちが開発・公開したライブラリやツールを利用し合う良い文化を形成し、コミュニティの発展に寄与しました。初期にGoチームが示したシンプルさを大切にする文化はコミュニティに浸透しています。たとえば、ジェネリクスを始めとする新しい機能を導入する際にもGoの良さであるシンプルさを破壊しないよう慎重に議論されています。

Goコミュニティは、Go ConferenceやGopherConなどの大規模カンファレンスや中小規模の勉強会(Meetup)を通して、初学者から上級者までさまざまなバックグラウンドを持つ人に学習の機会を提供しています。カンファレンスでは、各企業の導入事例やノウハウを共有し、日々の勉強会ではライブラリの情報交換や作ったものを披露する場、初学者が学ぶ場として活用されています。Goコミュニティがあることで、安心して自社の製品開発にGoを導入できます。

Goのリリースサイクルと安定性

2012年にGo1がリリースされたときに、Go1.x系の間は言語仕様や標準ライブラリに破壊的変更を加えず、後方互換を保つというルールができました。このルールはセキュリティの問題や重大な仕様バグなどの一部例外を除き、必ず守られる必要があります。しかし、goツールをはじめとする周辺ツールはこの限りではありません。とはいえ、周辺ツールであっても大きな変更が加わる場合は、2〜3回のリリースをかけて、ゆっくり移行していくことが多いです。

後方互換の保証は、Goを導入する企業にとっても重要です。Goはバージョンアップの度にランタイムや実行時のパフォーマンス改善を行ってきています。Goのバージョンアップはベネフィットが高いため積極的に行っていきたいところですが、バージョンアップによって製品開発に支障をきたすと本末転倒になってしまいます。後方互換とそこからくる安定性によって、Goはバージョンアップ時に既存コードのデグレなどがほとんど起こりません。

公式Wikiにあるように、Goは年に2回、2月と8月に新しいバージョンをリリースします。執筆時点の最新バージョンである1.19は、2022年8月にリリースされました。リリースサイクルが決まっていることも、安定してバージョンアップが行える理由になります。2022年の3月にリリースされた1.18はこの10年で最大のリリースであったため、1ヶ月ほど遅れてしまいましたが、これまでほとんど遅延はありません。

Go1.12〜Go1.13あたりでGo2という文脈で新しい機能が議論されたことがあります。そのため、Go2というワードだけが独り歩きし、Go2というバージョンで機能開発が進んでいるという印象を持ってる方もいるかもしれません。しかし、公式ブログによるとGo1の開発ラインで開発が進んでいき、その中で後方互換が崩れる止む得ない状況になった場合に、Go2としてリリースされるとされています。これは安定稼働しているGo1のエコシステムを維持し、独立した2つのメジャーバージョンの開発ラインができてしまうことを防ぎます。実際にGo2の文脈で議論されていた、エラー値やエラー処理、ジェネリクスなどは提案が出され、承認されたものはGo1としてリリースされました。そのため、Go2という話題は誤解を含んでいる可能性があるため、慎重に扱うと良いでしょう。

このように、Goは安定して利用できるように工夫して保守されています。単に言語機能を増やすのではなく、必要性や安定性、当初のGoが開発された目的である2つのスケーラビリティが崩れないかよくコミュニティで議論された上でアップデートされます。もちろん、コンパイラやランタイムの改善により、生成するコードのパフォーマンスの向上も行っています。

これまでのGo

Go1がリリースされて以来、多くの機能が開発され導入されてきました。特にgo get、モジュール、コンテキスト、ジェネリクスなどは開発者に大きな影響を与えました。

go getはビルドに必要なリソースを取得するコマンドです。Go1のリリース当初では、GOPATH以下にライブラリやコマンドラインツール(現在はgo installがその役目を担っている)をインストールする役目を持っていました。GOPATHとはGoのソースコードが管理されるディレクトリを指し、モジュール機能がリリースされて以降、あまり直接触れることは少ないでしょう。また、コマンドラインツールのインストールは最新のGoではgo installが役割を担っています。

Go1リリースより前からGoの動向を追っていた筆者にとってgo getは革新的な機能でした。Go1がリリースされる前は、makeコマンドを使ってソースコードをビルドしており、依存関係もMakefileに記述する必要がありました。インポートパスにGitHubなどのソースコードを管理しているVCS(バージョン管理システム)の情報を含めることで、ソースコードだけを読めば依存関係を解決できます。go getは依存するパッケージも取得できたため、他者の作ったライブラリを気軽に利用できるようになりました。このエコシステムのおかげでさまざまなOSSが開発され、コミュニティが発展していきました。

go getは革新的な機能でしたが、解決していない問題もありました。それは依存ライブラリのバージョン管理です。Go1.5でvendorディレクトリの機能がリリースされ、依存ライブラリを特定の状態をベンダリングできるようになりました。しかし、それではコミュニティの要望を十分に満たすことはできず、サードパーティ製のバージョン管理ツールやdepなどが生まれました。最終的にはgoツールに組み込まれる形で提供されたモジュール機能(go mod)によって依存モジュールのバージョン管理ができるようになりました。

Goの特徴の1つに並行処理があります。並行処理を行う上で難しいのは、正しくすべてのゴルーチンを終了させることです。うまく終了させないとリークが発生してしまいます。Go1.7で導入されたコンテキストの機能を使うことで大量のゴルーチンであっても安全にキャンセル処理(終了処理)が行えます。コンテキストについては よくわかるcontextの使い方や、エキスパートたちのGo言語の序章を参照すると良いでしょう。なお、ゴルーチンリークはuber-go/goleakのようなツールを使って見つけることもできます。

2022年3月にリリースされたGo1.18では、Go1リリース時から議論されてきたジェネリクス(型パラメータ)が導入されました。長い期間議論されてきたこともあり、後方互換を崩さず、Goらしい形でリリースされました。コミュニティでもまだまだジェネリクスの利用方法を模索している段階でもあるため、標準パッケージにおいても公開されている機能でジェネリクスを利用しているのはGo1.19でリリースされたatomic.Pointer型だけです。x/exp/slicesパッケージx/exp/mapsパッケージは便利ですが、実験段階であるため、今後のリリースに期待です。

これからのGo

2022年8月にGo1.19がリリースされ、次のリリースは2023年に予定しているGo1.20です。Go1.20ではGo1.18やGo1.19に入らなかったジェネリクス関係の変更のリリースが期待できます。ここではGo1.20でリリースされると決まった訳ではありませんが、将来リリースされることが期待できる3つの機能について解説します。なお、本稿の執筆後に仕様が変更されたり、機能自体がリリースされない場合もあります。

型エイリアスに型パラメータを使用できるように

1つめは、型エイリアスに型パラメータが使用できるようにする変更Issue#46477です。これが入るとジェネリクスの利用範囲が広がります。文字列をキーとした任意の型の値を取るマップ型を考えた場合に便利です。型エイリアスに型パラメータが使用できない場合、次のように別の型を定義して型パラメータを利用するしかありません。

type StringKeyMap[V any] map[string]V

しかし、StringKeyMap[int]型とmap[string]int型は別の型になってしまい扱いが面倒です。そこで次のように型エイリアスを利用するとStringKeyMap[int]型はmap[string]int型のエイリアスと解釈されるため、同じ型になります。同じ型であれば、型変換(キャスト)なしに値を相互にやり取りできます。

type StringKeyMap[V any] = map[string]V

x/exp/slicesパッケージやx/exp/mapsパッケージの標準パッケージへの取り込み

2つめは、x/exp/slicesパッケージやx/exp/mapsパッケージの標準パッケージへの取り込みです。これらのパッケージは十分実用性もあり、取り込まれる可能性は高いですが、必ずしも今(執筆時)と同じ形で取り込まれるとは限りません。標準のイテレータインタフェースの議論Discussion#54245も始まっており、この議論と合わせて標準パッケージへの導入が考慮されるかもしれません。

イテレータインタフェース、iter.Iter[E]型は次のような定義が提案されており、このインタフェースを満たすとfor range文を使った繰り返し処理に利用できるとされています。なお、Go1.19では、配列、スライス、マップ、チャネル、文字列のみがfor range文に利用できます。

package iter

type Iter[E any] interface {
	Next() (elem E, ok bool)
}

この提案が承認され、リリースされると次のようにdatabase/sql.Rows型のスキャンが簡単に行えるかもしれません。イテレータはデータベース接続のように、外部リソースにアクセスしながら繰り返し処理を行う場合も考えられます。議論Discussion#54245においても、StopメソッドやRangeメソッドなどリソースの開放についても言及されています。for range文を利用することで、リソースの開放を自動で行ってくれるようになると便利です。

rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
if err != nil { /* エラー処理 */ }
for user := range rows {
	// 1レコードあたりの処理
}

イテレータインタフェースの議論は始まったばかりです。筆者はGo1.20でリリースされる可能性は低いと考えていますが、期待しています。

Go公式の脆弱性管理システム

3つめは、Go公式の脆弱性管理システムです。すでにドキュメントはGo公式のセキュリティに関するページに記載されています。作業中(Work in progress)のようですが、Go Vulnerability Managementを見ると脆弱性データベースや静的解析ツールについて図を用いて紹介しています。

脆弱性管理システムは、脆弱性データベースやそのクライアント、静的解析ツールCLIツールAPIがそれぞれモジュール化されており、利用者の組織に合わせた導入が可能で、別の脆弱性管理システムを利用している場合も統合しやすいように配慮されているようです。

静的解析を行うvulncheck API は、脆弱性のあるモジュールを利用しているかソースコードまたはバイナリを解析します。ソースコードの解析では静的単一代入(SSA, Static Single Assignment)形式に変換し、Variable Type Analysis (VTA)アルゴリズムを用いてコールグラフを取得し、該当の関数やメソッドが実行されているか解析します。インポートしているかを解析するパッケージレベルの解析より、より正確に脆弱性に対する深刻さや状況を把握できます。

おわりに

本稿ではGoのこれまでとこれからについて紹介しました。Goは2つのスケーラビリティを高めるために開発され、安定して利用できるように配慮されています。しかし、コミュニティからの要望を軽視している訳ではなく、年に2回の開発者向けのアンケート調査や機能提案を受け付けることで利用者が本当に必要としている機能や改善を行うようにしています。

プログラミング言語としての機能追加だけではなく、セキュリティなどのGoを開発する上でのエコシステムの改善にも取り組まれています。新しい機能や改善については、Goを利用する誰もが参加できます。各地域にあるGoコミュニティや一般社団法人Gophers Japanが主催するGo Conferenceやリリースパーティ(新しいGoのバージョンを祝う勉強会)などに参加すると良いでしょう。新しい機能や今後のアップデートについて知ることができます。

本連載を通してもGoの今アツい話について触れていきます。どうぞ楽しみにしていてください。

おすすめ記事

記事・ニュース一覧