はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Go言語】暇なので公式ドキュメントを見ながらGo言語を入門してみた(Hello, WorldからREST APIまで)

インストール

インストーラーのダウンロード

以下のサイトを開き、1. Go downloadにあるインストーラーのダウンロードボタンを押します。
go.dev

インストール

インストーラーを起動し、指示に従ってください。

正しくインストールすることができれば、コマンドプロンプトやターミナル等で以下のコマンドが動くはずです。

$ go version

Hello, Worldをしてみる

プログラムの最初といえばHello, Worldですよね。

go.modについて

Go言語のコードを記述する作業フォルダのルートディレクトリにはgo.modという依存するモジュールを記述するUTF-8のテキストファイルを定義する必要があります。

A module is defined by a UTF-8 encoded text file named go.mod in its root directory. The go.mod file is line-oriented. Each line holds a single directive, made up of a keyword followed by arguments.

// DeepL翻訳
モジュールは、ルートディレクトリにあるgo.modというUTF-8でエンコードされたテキストファイルで定義されます。go.mod ファイルは行指向です。各行は、キーワードと引数で構成される一つのディレクティブを保持します。

Go Modules Reference - The Go Programming Language

// go.modのサンプル
module example.com/my/thing

go 1.12

require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]

記法に関しては各々で調べてほしいですが、modulegorequirereplaceexcluderetractは予約語のようですね。

誰も伝わらないと思いますが、Unityで言うところのmanifest.jsonに近いものを感じますね。

またモジュールってそもそもなんやねんとなりそうですが、公式ドキュメントには以下のように記述されています。

A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers.

A module is identified by a module path, which is declared in a go.mod file, together with information about the module’s dependencies. The module root directory is the directory that contains the go.mod file. The main module is the module containing the directory where the go command is invoked.

Each package within a module is a collection of source files in the same directory that are compiled together. A package path is the module path joined with the subdirectory containing the package (relative to the module root). For example, the module "golang.org/x/net" contains a package in the directory "html". That package’s path is "golang.org/x/net/html".

// DeepL翻訳
モジュールは、リリースされ、バージョン管理され、一緒に配布されるパッケージの集合体です。モジュールは、バージョン管理リポジトリから直接ダウンロードすることもできますし、モジュールプロキシサーバーからダウンロードすることもできます。

モジュールは、モジュールの依存関係の情報とともに go.mod ファイルで宣言されるモジュールパスによって識別されます。モジュールルートディレクトリは、go.mod ファイルを含むディレクトリです。メインモジュールは、go コマンドが起動されるディレクトリを含むモジュールです。

モジュール内の各パッケージは、同じディレクトリにあるソースファイルをコンパイルしたものです。パッケージのパスは、モジュールのパスにそのパッケージを含むサブディレクトリを加えたものです(モジュールルートからの相対パス)。例えば、"golang.org/x/net "というモジュールは、"html "というディレクトリにパッケージを含んでいます。そのパッケージのパスは「golang.org/x/net/html」です。

Go Modules Reference - The Go Programming Language

go.modを生成する

というわけでgo.modを作成していきましょう。

正直そこまで記述量があるわけではないので自力でファイルを生成して記述しても良いとは思いますが、一応コマンドで生成することができます。

$ cd 作業したいフォルダ
$ go mod init example/hello

example/helloはサンプルに記述されていたものをそのまま使ってます。別に他の好きな文字列でも構いません。

無事にコマンドが実行できるとgo.modが作成されているはずです。

// フォルダ構造
作業しているディレクトリ
|---- go.mod
// go.modの中身
module example/hello

go 1.19

hello.goを作成&記述する

あとは〇〇.goファイルを作成して、コードを書いていきましょう。

私の場合はVisual Studio Codeを利用して進めていきます。

hello.goを生成し、以下のコードを記述します。

// フォルダ構造
作業しているディレクトリ
|---- go.mod
|---- hello.go
// hello.go
package main

import "fmt"

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

コードについてざっと解説すると以下の通り

  • メインパッケージを宣言する(パッケージは同じディレクトリにある全てのファイルで構成される)
  • fmtパッケージをインストールする(fmtパッケージ)
  • コンソールにHello, World!を表示するmain関数を実装する(mainパッケージを実行するとmain関数がデフォルトで実行される)

コードを実行する

作業しているディレクトリで以下のコマンドを打てばコードが実行されます。

$ go run .
Hello, World!

パッケージの利用方法を知る

fmtパッケージについては言われた通りにインストールして利用したと思いますが、自力でパッケージを探して利用する手順を説明しておきます。

pkg.go.devページを開き利用したいパッケージを探す

まずはpkg.go.devを開くと、利用できるパッケージの一覧が表示されます。
pkg.go.dev

例えばquoteと検索すると、その内容に適したパッケージがずらっと表示されるはずです。

その中にあるquote (rsc.io/quote/v4)というものを使いたくなったとします。
quote package - rsc.io/quote/v4 - Go Packages

コードに記述する

quote (rsc.io/quote/v4)をインストールするようにコードを記述します。

// hello.go
package main

import (
	"fmt"

	"rsc.io/quote/v4"
)

func main() {
	fmt.Println(quote.Go())
}

ただこれを記述しただけではエラーが出力されていると思います。といいますのもgo.modに正しいモジュールの依存が記述されていないからですね。

コードから自動でgo.modを更新してくれるコマンドがあるので、それを実行します。

$ go mod tidy
go: finding module for package rsc.io/quote/v4
go: downloading rsc.io/quote v1.5.2
go: downloading rsc.io/quote/v4 v4.0.1
go: found rsc.io/quote/v4 in rsc.io/quote/v4 v4.0.1
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

go mod tidy ensures that the go.mod file matches the source code in the module. It adds any missing module requirements necessary to build the current module’s packages and dependencies, and it removes requirements on modules that don’t provide any relevant packages. It also adds any missing entries to go.sum and removes unnecessary entries.

// DeepL翻訳
go mod tidy は go.mod ファイルがモジュール内のソースコードと一致することを確認します。現在のモジュールのパッケージと依存関係をビルドするために必要なモジュールの要件を追加し、関連するパッケージを提供しないモジュールの要件を削除します。また、go.sum に不足しているエントリを追加し、不要なエントリを削除します。

Go Modules Reference - The Go Programming Language

無事に実行できると、go.modの中身が更新されるとと共にgo.sumファイルが生成されていることが確認できます。

// go.mod
module example/hello

go 1.19

require rsc.io/quote/v4 v4.0.1

require (
	golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
	rsc.io/sampler v1.3.0 // indirect
)

ちなみにindirectとは完成的に依存しているパッケージのことで、quoteパッケージがrsc.io/samplerに依存して、samplerパッケージがgolang.org/x/textパッケージに依存して......といった具合です。
quote/go.mod at master · rsc/quote · GitHub
sampler/go.mod at master · rsc/sampler · GitHub

// 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/v4 v4.0.1 h1:i/LHLEinr65wwTCqlP4OnMoMWeCgnFIZFvifdXNK+5M=
rsc.io/quote/v4 v4.0.1/go.mod h1:w/DafQky66grMesu3uPhdDMS3knhBippwwemZtMOyCI=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
コードを実行する

最後はいつものコードを打ち、実行します。

$ go run .
Don't communicate by sharing memory, share memory by communicating.

Go言語の基礎の基礎

関数

関数は0個以上の引数を取り、型名を後ろに書きます。

func add(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

またx, y intx int, y intの省略です。

複数の戻り値

func swap(x, y string) (string, string) {
	return y, x
}

named return value

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

戻り値となる変数に名前をつける(named return value)ことができます。
戻り値に名前をつけると、関数の最初で定義した変数名として扱われます。

変数定義

func main() {
	var i, j int = 1, 2
	k := 3
	c, python, java := true, false, "no!"
}

var:=を使います。
:=は暗黙的な型宣言です。

組み込み型

bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
// Unicode のコードポイントを表す

float32 float64

complex64 complex128

A Tour of Go

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

Zero values(デフォルト値)

変数に初期値を与えずに宣言すると、ゼロ値( zero value )が与えられます。

ゼロ値は型によって以下のように与えられます:

数値型(int,floatなど): 0
bool型: false
string型: "" (空文字列( empty string ))

A Tour of Go

func main() {
	var i int
	var f float64
	var b bool
	var s string
}

型変換

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
i := 42
f := float64(i)
u := uint(f)

定数

func main() {
	const World = "世界"
	const Truth = true
}
  • 文字(character)
  • 文字列(string)
  • boolean
  • 数値(numeric)

のみで利用可能。

for文

for i := 0; i < 10; i++ {
	sum += i
}

while文

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

無限ループ

for {
}

if文

if x < 0 {
	return sqrt(-x) + "i"
}
func pow(x, n, lim float64) float64 {
	// 評価するための簡単なステートメントを書ける
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}
if v := math.Pow(x, n); v < lim {
	return v
} else {
	fmt.Printf("%g >= %g\n", v, lim)
}

switch文

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

Go では選択された case だけを実行してそれに続く全ての case は実行されません。 これらの言語の各 case の最後に必要な break ステートメントが Go では自動的に提供されます。

Defer

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
...
defer へ渡した関数が複数ある場合、その呼び出しはスタック( stack )されます。 呼び出し元の関数がreturnするとき、 defer へ渡した関数は LIFO(last-in-first-out) の順番で実行されます。

ポインタ

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

変数 T のポインタは、 *T 型で、ゼロ値は nil です。
...
なお、C言語とは異なり、ポインタ演算はありません。

構造体

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4

	p := &v
	// (*p).Xの省略
	p.X = 1e9
	fmt.Println(v.X)
}
var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

Array

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"

	primes := [6]int{2, 3, 5, 7, 11, 13}
}

Slice

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

スライスは配列への参照のようなものです。

スライスはどんなデータも格納しておらず、単に元の配列の部分列を指し示しています。

スライスの要素を変更すると、その元となる配列の対応する要素が変更されます。

// 以下は等価
a[0:10]
a[:10]
a[0:]
a[:]

もっと厳密に書くとSliceは以下を保持しています。

  • 0番目の要素へのポインタ
  • スライスの要素数(len(slice))
  • スライスの容量(cap(slice))

要素数と容量はlencapを用いることで調べられます。

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s) // 6

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s) // 0

	// Extend its length.
	s = s[:4]
	printSlice(s) // 4

	// Drop its first two values.
	s = s[2:]
	printSlice(s) // 2
}

スライスのゼロ値(デフォルト値)はnilが格納されています。

func main() {
	var s []int
	printSlice(s) // len=0 cap=0 []

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s) // len=1 cap=1 [0]

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s) // len=2 cap=2 [0 1]

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s) // len=5 cap=6 [0 1 2 3 4]
}

Range

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

1つ目の変数はインデックス( index )で、2つ目はインデックスの場所の要素のコピー。

func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

簡単なREST APIを作成する

Ginというフレームワークを利用すれば簡単にREST APIを作成できるようなのでやってみます。
gin-gonic.com

ginのインストール

以下のコマンドを実行してginをインストールします。

$ go get -u github.com/gin-gonic/gin

コードの記述

// example.go
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}

あとはコマンドを打ってサーバーを起動し、ブラウザで0.0.0.0:8080/pingにアクセスしてください。

$ go run example.go
実験結果

エディタについて

Visual Studio Codeを最初は使っていましたが、Jet Brains社がGoLandというエディタを出していることを発見しました。

Jet BrainsRiderというエディタを私は愛用していたので、途中からGoLandを利用し始めたところ最高でした、

わざわざ自分でコマンドを打ってパッケージをインストールせずとも、エラーを修正とかで自動でやってくれたりと至れり尽くせりです。

気になる方は是非調べてみてください。
www.jetbrains.com