はじめに
こんにちは、雨の日が最近多く、気温の変化が激しくて、体調管理に気をつけなければと思うこの頃です。
さて、今回はGo言語についてです。Go言語をご存知の方は、最近ですと、2018年8月末に1.11がリリースされことや、Go2でのgenericsやエラーハンドリングに関する議論など耳にされている方も多いのではないでしょうか。
改めて話題になってたことで、私たちもGo言語を改めて学びなおそうと、勉強会合宿を開催しました。そこで発表・共有した内容を簡単にまとめ、数回にわたり連載を予定しております。
今回の記事では、改めてGo言語を勉強してみたいという方向けに、Go言語概要と環境構築から、Go 1.11でexperimentalでリリースされた Modules について紹介したいと思います。
Go言語(Golang)とは
Go言語の連載最初の1回目の記事なので、簡単にGo言語についても説明します。
Go言語は、2009年にGoogleから発表されたオープンソース プログラミング言語です。当初は、Robert Griesemerと、UNIX開発に携わった著名な、Rob Pike、Ken Thompsonの3名によって開発が進められ、下記のような特徴を持っています。
シンプルな構文
Goは機能をシンプルに保つために、コア開発者全員が必要と思った機能だけを言語に取り込むという方針があるようです。例えば、三項演算子がなかったり、繰り返しをfor文だけに限定したりするなど、構文が最小限になっています。
また、if文の波括弧が省略不可など、開発者のよるコーディングのばらつきがなくなる工夫も言語機能として組み込まれています。
さらに、自動でコードをフォーマットする gofmt や goimports 、それからコードの静的解析をする go vet や golint など、コーディングに関して開発者をサポートするツール群が充実しています。(自動フォーマットツールは、フォーマットルールをカスタマイズする機能がなく、コーディングルールを言語として厳密に定めてる意図を感じます。)
コンパイル言語
Rubyなどのスクリプト言語とは異なり、コンパイルが必要な言語で、静的型付けの安全性があります。ただ、C++などの他のコンパイル言語と比べて、コンパイルがとても早く、スクリプト言語のような効率性もあります。また、様々なOS向けのクロスコンパイルをサポートしており、基本的にはコンパイルで生成された単一のバイナリファイルをデプロイすれば利用できます。
並行処理
スレッドモデルによる並行処理を、言語として標準で提供している goroutine / channel と言った機能を使うことで簡単に実装ができます。この goroutine などは改めてブログ記事にしたいと思います。
その他の特徴
割と新しい言語であるGo言語ですが、クラスというものがないということも特徴的だと思います。
オブジェクト指向的な表現をするためには、構造体(struct)を定義し、その構造体に対してメソッドを定義することで可能ですが、クラスの継承と言ったことはできません。これは、Go言語として、継承よりコンポジション(composition over inheritance)の設計思想があるようです。
その他、Goらしい書き方(Go Way)については、Effective Goという記事で色々と書かれています。ただ、まずは書いて見て、強力なツールである、 go vet や golint で警告を表示させ、理解しながら修正する方が学びが早いと思います。こういうところもGo言語の良さですね。
Go開発環境の構築
さて、Go言語の特徴の概要は、このくらいにして、次は開発環境の構築について、記載をしていきます。本記事の執筆時点で最新のGo 1.11を対象にしています。
Go言語はDockerを利用しての開発も多いと思いますが、インストールも簡単なので今回はローカルのMac PC上に構築する方法を紹介したいと思います。
Goのインストール
公式サイトからインストーラ(パッケージ)がダウンロードでき、それをインストールすればGo環境は完成です!
ただ、今回はMac PC上にインストールする際に、複数のGoバージョンを管理するための goenv というものを紹介します。 pyenv や rbenv とかと一緒の仕組みですので、詳細の説明は割愛して、インストール手順だけ記載します。
1. homebrewでgoenvをインストール
1 | $ brew install goenv |
2. ~/.bash_profileに追記
1 2 | $ echo 'eval "$(goenv init -)"' >> ~/.bash_profile $ source ~/.bash_profile |
3. Go 1.11.0をインストール
1 2 3 4 | $ goenv install -l # インストール可能なバージョンの一覧を表示 $ goenv install 1.11.0 # 一覧にある バージョン 1.11.0 をインストールする $ goenv global 1.11.0 # 1.11.0のバージョンに切り替える $ go version # go version go1.11 darwin/amd64 と表示されていればインストール成功 |
なお、上記では global でバージョンを1.11.0にしていますが、特定プロジェクト(ディレクトリ配下)だけで、バージョンを指定したい場合は、 local も利用できます。
1 2 | $ goenv local 1.11.0 $ $ cat .go-version # 1.11.0 と表示されます |
本当に、 rbenv とかと全く一緒の使い勝手ですね。
環境変数
下記のコマンドを実行するとGo言語の開発に関連する環境変数の一覧が表示されます。今回はその中でも気にかけておくべき環境変数を2つ紹介します。
1 2 3 4 5 6 | $ go env ... 省略 ... GOPATH="/Users/engines/go" ... 省略 ... GOROOT="/Users/engines/.goenv/versions/1.11.0" ... 省略 ... |
GOROOT
GOROOTは、Goがインストールされているディレクトリになります。
今回は、 goenv を使ってインストールしていますので、上記のようなパスになっています。 goenv でバージョンを切り替えると、GOROOTのパスも変更されます。基本的には個人で変更することはありませんが、Linux上でtarアーカイブなどでインストールした際には、展開先のディレクトリを指定することになるので知っておくと良いと思います。
GOPATH
GOPATHは、外部のパッケージなどのソースを取りまとめておくディレクトリを指定するものになります。
デフォルトでは、 $HOME/go となっています。また、外部パッケージ以外に自身のプロジェクト開発ディレクトリをGOPATH配下にされている方もいますが、今回はあとで紹介する Modules の利用もあり、開発ディレクトリは、GOPATHの外とし、かつ開発プロジェクト別にGOPATHを管理できるようにしたいと思います。そこで、 direnv というツールを紹介します。
direnv
direnv は、特にGo言語開発には関係のないツールですが、簡単に説明するとディレクトリ毎に環境変数を定義できるツールです。今回は各プロジェクト毎の開発ディレクトリのみで固有のGOPATHを設定してみたいと思います。 hello_go というプロジェクトを開発することを想定して、 hello_go というディレクトリ配下で開発をする場合を想定してみます。
direnv のインストールし、 .bash_profile に追記する
1 2 3 4 | $ brew install direnv $ echo 'export EDITOR=/usr/bin/vim' >> ~/.bash_profile $ echo 'eval "$(direnv hook bash)"' >> ~/.bash_profile $ source ~/.bash_profile |
プロジェクト開発ディレクトリを作成し、環境変数を設定する
1 2 3 | $ mkdir hello_go; cd hello_go $ direnv edit . # vimが起動するので下記を保存 export GOPATH=$(pwd)/go # $(pwd)/../goとして親ディレクトリでも良いかも |
保存後に自動で読み込みが発生し、下記が出力されます。
1 2 | direnv: loading .envrc direnv: export +GOPATH |
これで、GOPATHの設定も完了です。実際は、gitでこのディレクトリを管理することになると思いますので、GOPATHのディレクトリは .gitignore に追加しておくことになると思います。
パッケージ バージョン管理
Go言語での開発はこれで始められますが、その前にもう一つパッケージの管理方法についても説明したいと思います。
Go言語は、インストールした時点の標準パッケージだけでも十分に開発が可能です。Webアプリケーションも標準パッケージだけで十分に開発できてしまいます。とはいえ、プロジェクトによっては、外部のオープンソースパッケージも利用したいことももちろんあると思います。そこで、外部のパッケージをインストールし、バージョンや依存などを管理するための機能が必要になります。
Go言語で外部パッケージを利用する際には、depといったような外部ツールを利用して、依存関係などパッケージを管理するがメジャーとなっています。
(標準の go get コマンドでgithubのリポジトリからコードを $GOPATH/src 配下にダウンロードして利用する方法もあります。)
depもまだまだ現役ではありますが、今回は、Goの標準として利用されるようになるだろう、 Modules を使ってみたいと思います。
少し雑な説明になりますが、Railsなどをご存知の方であれば、gemを想像していただければ良いかと思います。
Modulesとは
Modules は、「Goのパッケージには、バージョン付けが必要だ」ということで、Goのメインリポジトリの外で開発がスタートした vgo ツールが元になっています。(詳しくはこちらに和訳記事があります。)
vgo で一定の検証と評価がされ、2018年7月12日にGoのメインリポジトリに組み込まれました。そして、8月末にGo 1.11のexperimentalとしてリリースされました。また、1.12で正式リリース予定となっており、 dep から Modules への円滑な移行方法も検討されていることから、今後はこちらがデファクトになりそうです。
パッケージの追加
実際に Modules を利用して、外部パッケージを追加してみましょう。
1. Modulesの初期化
先ほど、作成した hello_go ディレクトリで実行します。重要なのは、環境変数の GOPATH で指定されたディレクトリ外で実行することです。
1 2 | $ go mod init github.com/re-engines/hello_go go: creating new go.mod: module github.com/re-engines/hello_go |
上記を実行すると go.mod というファイルが生成されます。このファイルにはインストールされた外部パッケージとそのバージョンが記載され、このファイルを配布することで他の開発者も同じバージョンのパッケージをインストールすることができます。(Gemfile.lockと同じような扱いですね。)
2. 外部パッケージの追加
Goのgithubのwikiにあるサンプルと同じですが、main.goというファイルを作成し、下記を実行しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | $ cat main.go package main import ( "fmt" "rsc.io/quote" ) func main() { fmt.Println(quote.Hello()) } $ go build go: finding rsc.io/quote v1.5.2 go: downloading rsc.io/quote v1.5.2 go: finding rsc.io/sampler v1.3.0 go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: downloading rsc.io/sampler v1.3.0 go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c $ ./hello_go 99 bottles of beer on the wall, 99 bottles of beer, ... $ cat go.mod module github.com/re-engines/hello_go require rsc.io/quote v1.5.2 $ cat go.sum golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y= rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= |
main.goファイルで外部パッケージをimportさせて、buildを実行すると依存している外部パッケージがダウンロードされ、go.modへの追記とgo.sumファイルが生成されます。
go.modファイルには、必要とされる外部パッケージとバージョンが追記されているのがわかると思います。これで、go.modを配布するだけで各開発者の環境で同じパッケージが利用されるようになります。
では、go.sumファイルと何でしょうか。これは、ダウンロードされたパッケージのchecksumになります。後ほど、紹介するコマンドでダウンロードされたパッケージの検証をする際に利用されます。なお、パッケージは $GOPATH/pkg 配下にダウンロードされています。
その他のコマンド
go mod tidy
tidy は整頓という意味ですが、これを実行すると不要なパッケージを削除し、go.modにある必要なパッケージをインストールします。
go test ./…
go mod tidy を実行後に上記コマンドを実行するとダウンロードしたパッケージのテストコードを実行してくれます。
go verify
go.sumファイルを使い、ダウンロードされたパッケージが正しいものかを検証します。
go get -u
これは、go.modファイルにあるパッケージのアップグレードを行います。
go get -u は、パッケージを最新のマイナー及びパッチリリースへアップグレードをし、 go get -u=patch は、最新のパッチリリースへアップグレードを行います。
補足1:ビルドと実行
先ほど、main.goを実行するために、 go build でコンパイルし、生成したバイナリファイルを実行した例を紹介しました。実は、Go言語ではとりあえずの試し実行であれば、コンパイルでの実行バイナルファイルを生成しなくても動作を確認できるのです。
Go言語では、mainパッケージのmain関数がエントリーポイントとなりますので、そのファイルを直接指定すると実行を確認できます。
1 2 | $ go run main.go 99 bottles of beer on the wall, 99 bottles of beer, ... |
補足1:packageとimport
Go言語では、変数や関数など全てのプログラム要素は、何らかのパッケージに属することになります。今回の例ではプロジェクトのトップ階層に作成したmain.goの一行目に package main という記載があったと思いますが、これがそのパッケージになります。mainパッケージは特別な意味を持ちますが、プロジェクトが大きくなりディレクトリ階層などが作成された場合は、パッケージ名はディレクトリ名と同じにするのが通例のようです。
importは、ファイル内で使用するパッケージを指定するためのものです。importでパッケージを指定することでそのパッケージの公開関数や変数をそのファイル内で使用することができるようになります。今回の例では、標準パッケージのfmtと外部パッケージの rsc.io/quote を利用しました。
さいごに
いかがでしたでしょうか。Go言語のブログ連載の1回目は、まずは実行するための環境構築を説明させていただきました。私たちもGo言語を色々と勉強し、実際に使ってみることで、Goならではの面白さを感じることができました。ぜひ、皆さんもGo言語を学んでみてはいかがでしょうか。
2回目は、基本構文などの説明を予定していますので、ぜひご覧いただければと思います。
Go記事の連載などは、こちらをご覧ください。