はじめに
今年はGo言語を始めてみるぞと目標だけ立てたまま気づいたら12月になってました。
これじゃいかん!というわけで、滑り込みで、重い腰をあげて、アドベントカレンダー担当当日に "A Tour of Go" をやってみました。
この記事はその体験レポートというかひとりごとの議事録みたいなものです。
自分と同じように「Go言語が気になるけど触ったことない」という人はこの記事を見てやった気になってみるのもいいんじゃないでしょうか?
A Tour of Go とは
"A Tour of Go" はGo言語の公式チュートリアルで、ブラウザ上でコードを動かしながらGo言語のエッセンスを体験できます。
日本語訳版も公開されています。
ツアーの構成は以下のようになっていて、ハローワールドから基本構文・型の話、最後のほうはGo言語特有の仕様に関する話がでてきます。
※2025-12-02時点
Using the tour
Welcome!
Hello, 世界
Go local
Go offline
The Go Playground
Congratulations
Basics
Packages, variables, and functions.
Packages
Imports
Exported names
Functions
Functions continued
Multiple results
Named return values
Variables
Variables with initializers
Short variable declarations
Basic types
Zero values
Type conversions
Type inference
Constants
Numeric Constants
Congratulations!
Flow control statements: for, if, else, switch and defer
For
For continued
For is Go's "while"
Forever
If
If with a short statement
If and else
Exercise: Loops and Functions
Switch
Switch evaluation order
Switch with no condition
Defer
Stacking defers
Congratulations!
More types: structs, slices, and maps.
Pointers
Structs
Struct Fields
Pointers to structs
Struct Literals
Arrays
Slices
Slices are like references to arrays
Slice literals
Slice defaults
Slice length and capacity
Nil slices
Creating a slice with make
Slices of slices
Appending to a slice
Range
Range continued
Exercise: Slices
Maps
Map literals
Map literals continued
Mutating Maps
Exercise: Maps
Function values
Function closures
Exercise: Fibonacci closure
Congratulations!
Methods and interfaces
Methods and interfaces
Methods
Methods are functions
Methods continued
Pointer receivers
Pointers and functions
Methods and pointer indirection
Methods and pointer indirection (2)
Choosing a value or pointer receiver
Interfaces
Interfaces are implemented implicitly
Interface values
Interface values with nil underlying values
Nil interface values
The empty interface
Type assertions
Type switches
Stringers
Exercise: Stringers
Errors
Exercise: Errors
Readers
Exercise: Readers
Exercise: rot13Reader
Images
Exercise: Images
Congratulations!
Concurrency
Concurrency
Goroutines
Channels
Buffered Channels
Range and Close
Select
Default Selection
Exercise: Equivalent Binary Trees
Exercise: Equivalent Binary Trees
sync.Mutex
Exercise: Web Crawler
Where to Go from here...
見出しだけみると大量に見えますが、この記事を書きながら進めても12時間くらいで終わったので、ざっと把握するだけなら8時間くらいあれば終わると思います。
また途中にいくつかエクササイズがありますが、Go言語の構文どうこうというよりはアルゴリズムを考える問題という感じなのでスキップしてもよいと思います。
体験レポート
ツアーを進めて思ったことをつらつらと書いていこうかなと思います。
Hello, 世界
やはり最初は "Hello, World!" ですか。動くコードの最小限はこうなんですね。
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
パッケージの定義、インポート文、関数定義があって、mainという名前をつけた関数が実行されるという感じですかね。
あとは説明にある gofmt というフォーマッタ。これが公式に提供されている(内包されている)のがGo言語の良い点って誰かが言ってました。
Packages
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
パッケージ名は小文字始まりで、関数名は大文字始まりなんですね。でもmainは小文字?
と思ったら先の "Exported names" の章で説明されてたけど、大文字始まりの関数(と定数)がエクスポートされる仕様らしい。
サブパッケージの仕組みもある。
ただ math.rand.Intn(10) みたいに上位パッケージだけimportしてメソッドを呼ぶのはビルドエラーでダメだった。
あとは使ってないimportがあるとビルドエラーになるのね。個人的にいい仕様だと思う。
サブパッケージどうやってつくるんだろうと軽くググってみた感じ、少し面倒そうな雰囲気。
Imports
importを個別に書く方法もあるらしいけど、Goはグループ化する方を推奨してるっぽい。じゃあそっちを使うようにします。
import (
"fmt"
"math"
)
import "fmt"
import "math"
Functions
関数の書き方はTypeScriptに近い。コロンがないのはちょっと慣れなさそう。
func add(x int, y int) int {
return x + y
}
// 同じ関数をTypeScriptで書くとこんな感じ
function add(x: number, y: number): number {
return x + y;
}
Functions continued
複数の引数の型が同じ場合は省略できるらしい。でも全部同じとかでない限りはちゃんと書いたほうがよさそう。
func add1(x, y int) int { return x + y }
func add2(x int, y int) int { return x + y }
func add3(x, y int, z float64) int { return x + y + int(z) } // こういうのは避けたほうがいいかも?
func addx(x, y int) int { return x + y }
あと関係ないけど、関数定義は1行に収めても問題ないみたいで、フォーマッタをかけたら波かっこの開始でそろえてくれた。
Multiple results
タプルも返せるとのこと。
それで、説明はいっさいなかったけど、分割代入ができるのも重要なところ。
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
Named return values
戻り値の名前を最初に宣言しちゃう方法があるのね。
こうすることで関数定義部分だけ見れば関数の内容がわかるようになると。
// split関数はintのsumを引数にintのxとyを返す関数
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
とはいえ、これ正直使わないほうがよさそう。説明にもこう書かれてるし。
naked returnステートメントは、短い関数でのみ利用すべきです。長い関数で使うと読みやすさ( readability )に悪影響があります。
Variables
ここから変数の説明。
パッケージ内と関数内のスコープがあるのか。
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
ちなみにこれを実行すると 0 false false false って表示される。型ごとに初期値を持つタイプね。
あとimportと同じく変数宣言でも未使用の変数があるとビルドエラーになるのね。
Variables with initializers
初期化しつつ宣言も当然あると。んで、その場合は型は省略可能と。
var i, j int = 1, 2
var c, python, java = true, false, "no!"
Short variable declarations
関数内でのみ、型を省略した初期化つきvar宣言を := に置き換えられるっぽい。
ローカル変数に関数の戻り値を入れるときとかのショートハンドってことかな。
// a := 1 // NG
func main() {
k := 3
// k int := 3 // NG
}
Basic types
組み込み型は以下の通り。(転記)
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 の別名
rune // int32 の別名
// Unicode のコードポイントを表す
float32 float64
complex64 complex128
まあうん。そういう感じね。
rune型はJavaでいうchar型みたいなことかな?
stringをrune配列にキャストすることで日本語でも文字数を正確に求める、みたいな使い方があるっぽい。
恥ずかしながらcomplex型を知らなかったのでググってみると複素数型とのこと。まあ自分が使う機会はないだろうな...
というかここで説明するの?って思ったけど、varの変数宣言もimportと同じようにまとめてできるのね。
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
あと使えそうな情報として、Printfで %T を使うことで型名を取得できるのね。
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
//→ Type: bool Value: false
Zero values
Variablesの章で気づいてたけど、組み込み型はゼロ値を持ってる。
数値は0、真偽値はfalse、stringは""(空文字列)
Type conversions
型名(値) でキャスト。
暗黙的な型変換はされない(ビルドエラーになる)のは健全かも。
こういう変換もダメだった。互換性はないという扱いなのかね。
var a = bool(0)
var b = bool("true")
var c = string(true)
var d = string(true)
var e = int("100")
Type inference
どの値がどの組み込み型に型推論されるか、いくつか試してみた。
v := true // bool
v := "a" // string
v := 'a' // int32
v := 12 // int
v := 3.14 // float64
v := 1e+1 // float64
v := 0.5i // complex128
Constants
例では const World = "世界" となってるのでエクスポート有無にかかわらず定数はアッパーキャメルなのかも。
Numeric Constants
定数は型が決まってなくて値の情報だけを持つらしい。
つまり例でいうとSmallは2という情報しかないからint型を引数にとる関数でもfloat型を引数にとる関数でも使える。ってことかな?intからfloatへのアップキャストできますみたいな話?
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100 // = 2^100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99 // = 2^1
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 { return x * 0.1 }
func main() {
fmt.Println(needInt(Small))
// fmt.Println(needInt(Big)) // これはNG
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
ちなみに上記のNG部分はコメントを外すと以下のエラーが出る。ビルドエラーでここまででるのか。
./prog.go:20:22: cannot use Big (untyped int constant 1267650600228229401496703205376) as int value in argument to needInt (overflows)
Go build failed.
For
ここから制御構文の話。まずはfor文から。
for i := 0; i < 10; i++ {
sum += 1
}
for 初期化ステートメント; 条件式; 後処理ステートメント { の形。
うーん、個人的には丸かっこがあったほうが見やすいかも。(つけるとエラー)
For continued
初期化ステートメントと後処理ステートメントはなくても問題ないらしい。
for ; sum < 1000; {
sum += 1
}
For is Go's "while"
さらにセミコロンも省略可能で、つまり他言語でいうwhile文もfor文が兼ねている。
for sum < 1000 {
sum += 1
}
Forever
さらに条件式もなくても問題ない。その場合は無限ループになる。
for {
}
もちろん for true { でも同じ。
If
はい。(なにもいうことなし)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
If with a short statement
if文のブロックスコープ内と条件式で使える変数を用意できる。
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
Pythonにもセイウチ演算子としてあるけど、TypeScriptでもたまにほしくなる。
// 同じ式を2回評価することになってしまう
if (Math.pow(x, n) < lim) {
return Math.pow(x, n);
}
// if文の後ろでもvが生き残ってしまう
const v = Math.pow(x, n);
if (v < lim) {
return v;
}
If and else
if文の条件式で宣言した変数はif文のthenブロックだけじゃなくてelseブロックでも参照できると。
あと例には書いてないけど普通にelse ifもかけた。
if v := math.Pow(x, n); v < lim {
return v
} else if w := v * 2; w > 0 {
fmt.Printf("%g >= %g\n", v, lim)
} else {
return w
}
Switch
Go言語のswitch文はフォールスルーがないらしい。break不要。
あとcaseには値だけじゃなくて変数や式も使えるっぽい。特に式を使えるのは特徴的かも。
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)
}
}
もはや説明されてないけど、switchも変数の宣言を同時にできるっぽい。
フォーマットかけてもswitchとcaseのインデントが同じなのが若干気になる。
Switch evaluation order
break不要ということは上から順にみてマッチしたらそこで止まるということ。
おもしろそうなのはcaseに式を使ってる場合、たどり着かなかったら評価されないところ。
switch i {
case 0:
case f():
}
Switch with no condition
for文と同じで条件式は省略可能。その場合はcaseの条件式だけで判断される。なのでほぼif-else。
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
// 実質こう
if t.Hour() < 12 {
fmt.Println("Good morning!")
} else if t.Hour() < 17 {
fmt.Println("Good afternoon.")
} else {
fmt.Println("Good evening.")
}
}
見通しがいいからswitchのほうが好きかも。
Defer
謎の仕組みきた。
deferしておくと関数の終わりまで処理を遅延させるらしい。なので例を実行すると hello world の順で表示される。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
関数の引数はdeferを呼んだ時点で評価されるけど、関数自体は呼び出されたタイミングで評価されると。
try-catch-finally的な、Pythonのwithのexit的な、そういう用途なのかな。
Stacking defers
deferはスタックされるから、最後にdeferしたものから順に呼ばれる。
deferについてはGoの公式ブログを見てねってあるけど、軽く見た感じやっぱりファイルのCloseをdeferしておくみたいな使い方を想定しているっぽい。
そしてブログのコードの中にあって気づいたけど、無名関数(即時関数)もあるんだね...
var hoge = func() int {
return 12;
}()
Pointers
ポインタあるのか...
Java・TypeScript・Pythonばかり触ってたから、一番最初にC言語を勉強した以来かもしれない。
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer //→ 42
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i //→ 21
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j //→ 73
}
Structs / Struct Fields
構造体もあるんですか。今のところC言語じゃんという感想。
type Vertex struct {
X int
Y int
}
// 型を省略した形なら1行でもかける
type Vertex struct { X, Y int }
// 型が違う場合はセミコロンで区切って1行でかける。けどフォーマットかけると複数行の形になる。
type Vertex struct { X int; Y int }
func main() {
v := Vertex{1, 2}
fmt.Println(v) //→ {1 2}
v.X = 4
fmt.Println(v.X) //→ 4
}
Pointers to structs
さっきのコードでは v.X でアクセスできたけど、ポインタを通じて (*p).X でもOKと。(pに入ってる実体vのXの参照)
さらに、こういうときはアスタリスクを省略できるので例のように p.X で値を変えられるということらしい。
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9 // (*p).X = 1e9 でも同じ
fmt.Println(v)
}
Struct Literals
構造体の各要素はフィールド名を指定して任意に初期値を指定できるとのこと。
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
p1 = p
p2 = &v2
)
func main() {
fmt.Println(v1, p, v2, v3, p1, p2) //→ {1 2} &{1 2} {1 0} {0 0} &{1 2} &{1 0}
}
ポインタを戻すうんぬんの部分はいわゆる参照渡しの話だよね。
Arrays
配列の話。
Go言語では基本は固定長配列なのかな。
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1]) //→ Hello World
fmt.Println(a) //→ [Hello World]
primes := [6]int{2, 3, 5, 7, 11, 13}
// primes := [6]int{2, 3, 5} // こうした場合は [2, 3, 5, 0, 0, 0] ができあがる
fmt.Println(primes) //→ [2 3 5 7 11 0]
}
宣言だけだとゼロ値x長さの配列。
インデックスアクセスで書き換え。
初期値を与えてつつ宣言も可能っぽい。
Slices
開始を含み、終了を含まない範囲を返す。
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s) //→ [3 5 7]
}
Slices are like references to arrays
スライスはコピーじゃなくて参照。TypeScriptよりはJavaだ。
func main() {
names := [4]string{"John", "Paul", "George", "Ringo"}
fmt.Println(names) //→ [John Paul George Ringo]
a := names[0:2]
b := names[1:3]
fmt.Println(a, b) //→ [John Paul] [Paul George]
// ここでb[0]、つまりPaulをXXXに変更
b[0] = "XXX"
fmt.Println(a, b) //→ [John XXX] [XXX George]
fmt.Println(names) //→ [John XXX George Ringo]
}
Slice literals
初期リストをもつなら配列長は省略できるってことかな?
q := []int{2, 3, 5, 7, 11, 13}
(追記)
"Creating a slice with make" まで進んで理解した。配列とスライスで型が違うんだ。
↓は全部長さ3だけど、xは固定長3の配列リテラルで、yとzは現時点の長さが3なだけのスライスリテラル。
x := [3]int{0, 0, 0}
y := []int{0, 0, 0}
z := make([]int, 3)
fmt.Printf("x=%T, y=%T, z=%T\n", x, y, z) //→ x=[3]int, y=[]int, z=[]int
Slice defaults
スライスの開始終了は省略できると。省略したら0/lengthみたいなね。
長さが10なら a[0:10] = a[:10] = a[0:] = a[:] ってこと。
Slice length and capacity
capacityとかいう謎の概念が登場。
lengthはほかの言語でもおなじみの概念でスライスの長さを表すっぽい。
capacityはスライスの開始地点から元配列の末尾までの長さ?
func main() {
s := []int{2, 3, 5, 7, 11, 13} //→ len=6 cap=6 [2 3 5 7 11 13]
s = s[:0] //→ len=0 cap=6 []
s = s[:4] //→ len=4 cap=6 [2 3 5 7]
s = s[2:] //→ len=2 cap=4 [5 7]
}
len()とcap()は組み込み関数かな。
(追記)
"Creating a slice with make" まで進んで理解した。capは可変長配列だ。
- len=5, cap=5は長さ5の固定長配列
- len=0, cap=5は長さ5まで使える現在の長さ0の可変長配列
つまりlenがcapより大きくなることはない?
Nil slices
ここにきて新しい概念、nil。
null的なもので、空配列とは別物っぽい。さらにいうと長さ0の固定長配列とも別物っぽい。
func main() {
var a []int
fmt.Println(a, len(a), cap(a)) //→ [] 0 0
b := []int{}
fmt.Println(b, len(b), cap(b)) //→ [] 0 0
c := [0]int{}
fmt.Println(c, len(c), cap(c)) //→ [] 0 0
fmt.Printf("type: a=%v, b=%v, c=%v\n", a, b, c) //→ type: a=[]int, b=[]int, c=[0]int
// c == nil はビルドエラーになる
fmt.Printf("nil : a=%v, b=%v, c=%v\n", a == nil, b == nil, "") //→ nil : a=true, b=false, c=
}
Creating a slice with make
makeで、長さだけの指定であればスライスリテラルで一応代替できて、容量も指定する使い方だとメモリ確保的な意味合いになるのかな。
// どちらも長さ5のスライスを作成する
a := make([]int, 5)
a := []int{0, 0, 0, 0, 0}
// 長さは0で容量が5のスライスを作成する
b := make([]int, 0, 5)
スライス操作は「切り出す」というよりは「長さを与える」という認識のほうがいいかも?
「長さ0のスライスをスライスして長さ2にする」みたいな操作があるわけだし。
というか操作名もデータ名も両方スライスだからややこしいなw
Slices of slices
いわゆる多次元配列
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
ちなみにGo言語は配列の最後の要素にカンマがついてても問題ない言語みたい。地味にうれしいやつ。
Appending to a slice
引数のスライスに新しい要素を追加する破壊的メソッド。
スライスがnilでも問題ないのね。
var s []int
s = append(s, 0)
追加対象スライスの容量が足りないときは新しい配列を作るとあるので、たぶんそのケースだとメモリの再割り当てが発生してパフォーマンスが下がるみたいな話があるんだろうなぁ。
Range
foreach的なやつ。
pow := []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
rangeは関数とかではなくキーワードっぽい。
つまりJavaの拡張for文とかTypeScriptのfor-of構文みたいな。
あとポイントは2つ目の戻り値が要素のコピーである点。
なのでその戻り値を変更しても配列のほうに影響しない。
func main() {
posList := [](struct{ x, y int }){{0, 0}, {1, 1}, {2, 2}}
fmt.Println(posList) //→ [{0 0} {1 1} {2 2}]
for i, v := range posList {
fmt.Println(i, v)
v.x += 1 // こっちだと変化なし
// posList[i].x += 1 // こっちだと変化あり
}
fmt.Println(posList) //→ [{0 0} {1 1} {2 2}]
}
Range continued
Goは未使用の変数が許されないので使わない変数はアンダーバーで捨てる。
for _, v := range ([]int{1, 2, 3}) {
fmt.Printf("v=%v\n", v)
}
Maps
いわゆるキーバリューストア。
var m map[string]Vertex
// TypeScriptで表すとこんな感じ
let m: Record<string, Vertex>;
// Javaで表すとこんな感じ
Map<String, Vertex> m;
値にアクセスするには配列と同じく角かっこを使う。
type Pos struct { x, y int }
func main() {
var m map[string]Pos
fmt.Println(m == nil) //→ true
m = make(map[string]Pos)
fmt.Println(m == nil) //→ false
m["hoge"] = Pos{1, 2}
m["fuga"] = Pos{5, 3}
fmt.Println(m == nil) //→ false
fmt.Println(m)
}
Map literals / Map literals continued
配列と同じようにリテラルあり。
var m = map[string]Vertex{
"Bell Labs": Vertex{40.68433, -74.39967},
"Google": Vertex{37.42202, -122.08408},
}
型推論できる場合は型名を省略できるらしい。「mapに渡すトップレベルの型が単純な型名である場合は」というのがどこまでを指すのかピンとこないが。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
Mutating Maps
キーバリューの削除は組み込み関数を使う。すでに存在しないキーを指定しても問題ないっぽい。
m := make(map[string]int)
delete(m, "hogehoge")
それで、存在しないキーを参照した場合はぬるぽにはならず、参照式の2つ目の戻り値で成否のbool値が返ってくるらしい。へー。
m := make(map[string]int)
m["hoge"] = 100
// 存在するケース
if v, ok := m["hoge"]; ok {
fmt.Println("exist")
} else {
fmt.Println("not exist")
}
// 存在しないケース
if v, ok := m["fuga"]; ok {
fmt.Println("exist")
} else {
fmt.Println("not exist")
}
Function values
関数も変数として扱えるのは最近の言語ではデフォだよね。
add := func(x int) func(int) int { return func(y int) int { return x + y } }
a := add(1)(2)
fmt.Println("a =", a)
// TypeScriptにするとこんな感じ
const add = (x: number) => (y: number) => x + y
const a = add(1)(2)
console.log("a =", a)
Function closures
クロージャーも扱える。
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
Methods
ここからクラス相当の仕組みに関する話。
実際はクラスの仕組みはなくて、型にメソッドを定義することでクラスを実現している。
type Vertex struct {
X, Y float64
}
// funcと関数名の間の記述が型へのバインドで "レシーバ引数" というらしい
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
// TypeScriptで表すならこんな感じ?
class Vertex {
X: number
Y: number
constructor(x: number, y: number) {
this.X = x
this.Y = y
}
abs() {
return Math.sqrt(this.X * this.X + this.Y * this.Y)
}
}
v = new Vertex(3, 4)
console.log(v.abs())
Methods continued
構造体だけじゃなくて任意の型にメソッドを定義できるらしい。
ただし組み込み型や別パッケージの型に追加するのは無理。
// ラッパクラス的なものも作れそう
type String string
func (s String) startsWith(c rune) bool {
return []rune(s)[0] == c
}
func main() {
s := String("Hello, World!")
fmt.Println(s.startsWith('H'))
fmt.Println(s.startsWith('h'))
}
Pointer receivers
レシーバをポインタで定義すれば変更可能になるというわけね。
type Pos struct {
X, Y int
}
// 変数レシーバ
func (this Pos) GetX() int { return this.X }
func (this Pos) GetY() int { return this.Y }
// ポインタレシーバ
func (this *Pos) Set(x, y int) { this.X = x; this.Y = y }
func main() {
pos := Pos{1, 2}
fmt.Printf("(x, y) = (%v, %v)\n", pos.GetX(), pos.GetY()) //→ (x, y) = (1, 2)
pos.Set(3, 4)
fmt.Println(pos) //→ {3 4}
}
Pointers and functions
そもそも関数の引数を型にした場合は値渡しの挙動になって、参照渡しで副作用ありにするなら引数をポインタ型にして、呼ぶ側はアドレスを渡すのね。
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10) //←値ではなくアドレスを渡す
fmt.Println(Abs(v))
}
Methods and pointer indirection
あー。なるほど。
ポインタ型を引数にとる関数は呼ぶ側でアドレスを渡す必要があるからポインタレシーバも本当はアドレスで呼ぶべきなんだけど、利便性を考えて省略可能にしてたのか。
v := Vertex{}
v.Scale(5) // これで呼べるけど
(&v).Scale(10) // こう呼んでいるのに等しい
同様に型を引数にとる変数レシーバをアドレスに対してそのまま実行することもできるってわけだ。
v := Vertex{}
v.Abs() // こう呼ぶのが通常だけど
p := &v
p.Abs() // こう呼ぶこともできて
(*p).Abs() // こう呼んでいるのに等しい
Choosing a value or pointer receiver
ミュータブルな関数・メソッドをつくるためだけじゃなくて、イミュータブルな関数・メソッドをつくるときもメモリ効率化のためにあえてポインタレシーバを使うってのもあるよね。
とはいえミュータブルってことは副作用の可能性がでてきちゃうから、そこはトレードオフかね。
// イミュータブルなメソッドでもポインタレシーバでつくってよい
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
Interfaces
型がどんなメソッドを持つかの定義だね。
で、例のコードがエラーになるのは Abs メソッドはあくまで Vertex のポインタに定義されていて Vertex 自体には定義されてないので Abser の a と Vertex の v には互換性なしでエラー、ってことなんだね。
// *Vertex に Abs が定義されている
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// Abser は Abs をもってる
type Abser interface {
Abs() float64
}
func main() {
var a Abser
v := Vertex{3, 4}
a = &v // ここは問題なし
a = v // ここはエラー
}
先述したメソッド呼び出しではアドレス表記を省略できるって仕様のせい?でこの辺ややこしくなってる気がする。
Interfaces are implemented implicitly
明示的にinterfaceをimplementする必要はないのか。
となると、どこかしらでinterface型の変数にimplementしてるつもりの型の変数を割り当てる式を実装しておいて、ビルドが通るならimplementできてて、エラーになるならimplementできてないとわかる、って使い方かな。
type I interface {
M()
}
type T struct {
S string
}
func (t T) M() {
fmt.Println(t.S)
}
func main() {
// ここが通る → implementできてる
// ここが通らない → implementできてない
var i I = T{"hello"}
i.M()
}
Interface values
いわゆる抽象化かな。IはMというメソッドを持ってることだけ保証していて、何が呼ばれるかは前後の処理次第。
type I interface{ M() }
// T
type T struct{ S string }
func (t *T) M() { fmt.Println(t.S) }
// F
type F float64
func (f F) M() { fmt.Println(f) }
func main() {
var i I
i = &T{"Hello"}
i.M() // T#M()が実行される
i = F(math.Pi)
i.M() // F#M()が実行される
}
Interface values with nil underlying values
当然だけどGo言語にも実行時エラーはあって、いわゆる「ぬるぽ」は「にるぽ」なのねw
func (t *T) M() {
// if t == nil {
// fmt.Println("<nil>")
// return
// }
fmt.Println(t.S)
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x499534]
goroutine 1 [running]:
main.(*T).M(0x4e54c8?)
/tmp/sandbox601960936/prog.go:18 +0x14
main.main()
/tmp/sandbox601960936/prog.go:27 +0x25
Nil interface values
インターフェース型の変数を初期化しないままメソッドを呼んでもビルドエラーにはならず、実行時エラーになる。
type I interface {
M()
}
func main() {
var i I
i.M()
}
The empty interface
TypeScriptでいうany型というか、JavaでいうObject型みたいなものかも。なんでも入る。
func main() {
var i interface{}
i = 42
i = "hello"
}
Type assertions
見た目はドットアクセスっぽいけど全然違うね。勘違いしそう。
キャストというか、instanseofを使った型ガード的な用途かな?
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
ff := i.(float64) // panic
fmt.Println(ff)
}
2つ目の戻り値を取るか取らないかで挙動が変わるのはどういうことだろう?
まあ基本的にokをとるようにすればいいんだろうけど。
Type switches
パターンマッチですねぇ。これはうれしい。
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("値 %v はint型です。\n", v)
case string:
fmt.Printf("値 %v はstring型です。\n", v)
default:
fmt.Printf("値 %v の型は不明です。\n", v)
}
}
func main() {
do(21) // 値 21 はint型です。
do("hello") // 値 hello はstring型です。
do(true) // 値 true の型は不明です。
}
Stringers
Javaでいう toString メソッドのオーバーライドてきな話っぽい。Stringメソッドを実装しておけばPrintなどで採用される感じ。
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v is %v years old", p.Name, p.Age)
}
func main() {
a := Person{"Alice", 20}
z := Person{"Bob", 30}
fmt.Println(a) //→ Alice is 20 years old
fmt.Println(z) //→ Bob is 30 years old
}
Errors
Stringと同様に、Errorメソッドを定義しておくと実行時エラーになったときに2つ目の戻り値で何を返すか決められるってことかな。
func main() {
// i, err := strconv.Atoi("1")
i, err := strconv.Atoi("1st")
if err != nil {
fmt.Printf("NG: %v\n", err)
return
} else {
fmt.Println("OK:", i)
}
}
//→ NG: strconv.Atoi: parsing "1st": invalid syntax
Readers / Images
IO系のインターフェースと画像系のインターフェース?パッケージ?
なんでこれらがここで紹介されてるのか謎。気になるなら見ればいいかなくらいの感触。
Goroutines
Go言語の売りとしてよく聞くゴルーチン。
マルチスレッド処理を書くための構文って感じかな。
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
say("world")
say("hello")
//→ worldが5回表示されたあとにhelloが5回表示される
go say("world")
say("hello")
//→ helloが5回表示される間にworldも5回表示される
}
Channels
ここにきてchanという新しい組み込み型が登場。
乱暴な言い方をするとゴルーチンの各スレッドの結果を共有するためのグローバル変数的なもの?
ただ値の設定を待つことができるからただの変数ってわけでもない。
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 前半のsumと後半のsumの結果を待つ
fmt.Println(x, y, x+y)
}
仮にチャネルからの取得部分を
x, y, z := <-c, <-c, <-c
のように存在しないゴルーチンを待つようにすると以下の実行時エラーがでてきた。
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox1392388007/prog.go:20 +0x1de
Buffered Channels
チャネルをmakeするとき第2引数で長さを決められて、長さがあれば複数の値をチャネルに送信できる。
バッファはキューっぽい。先入れ先出しの動きになってる。
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) //→ 1
fmt.Println(<-ch) //→ 2
}
Range and Close
チャネルからの受信の2つ目の戻り値でチャネルの値有無が見れるみたいな書き方だけど、↓のように実装してもエラーになっちゃうなぁ。
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
if i, ok := <-ch; ok {
fmt.Println(i)
}
if i, ok := <-ch; ok {
fmt.Println(i)
}
// ここは実行されない想定だったけど以下のエラーになってしまった
// fatal error: all goroutines are asleep - deadlock!
if i, ok := <-ch; ok {
fmt.Println(i)
}
}
rangeなら存在する分をすべてとれるから、いったんはそっちのやりかたを覚えればいいかな。
Select
複数のチャネルのうち受信できるものから受信して処理するswitch文って感じかな。理解して使いこなすのは時間がかかりそう。
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
処理の流れ
- cから値を10回受信してコンソール表示し、quitに値を送信する処理をゴルーチンでサブスレッドに登録。
- fibonacciを実行
- selectでquitは空だから選択されず、cは空なのでxの送信が選択される。
- cに送信されたので1のゴルーチンでcから受信してコンソール表示される。
- 3と4を10回繰り返す。
- 10回ループを抜けてcからの受信がなくなってquitに0が送信される。
- selectでcは空じゃないので送信が選択されず、quitは空じゃないので受信が選択される。
- "quit"がコンソール表示される。
Default Selection
defaultもある。switch文とほぼ同じ感じ。
どのチャネルも準備できてないときの挙動を定義するのに使うのかな?
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
defaultがある場合の実行結果
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
BOOM!
defaultをコメントアウトした場合の実行結果
tick.
tick.
tick.
tick.
tick.
BOOM!
sync.Mutex
チャネルはあくまでゴルーチン間の通信用の変数。
通信は不要で単に変数を排他的に操作したいときのためにロックがある。
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
c.v[key]++
c.mu.Unlock()
}
// returnのあとにロックを解除しようと思ったらたしかにdeferを使うしかないね
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
Incのロック/アンロックをコメントアウトするとこんなエラーが発生したりしなかったりした。
fatal error: concurrent map writes
おわりに
というわけで A Tour of Go をやってみました。
自分が普段触ってる言語より低級寄りな感じがしましたが、型システムや開発速度の速さなんかはモダンな言語だなという感想です。
ゴルーチンを使いたいかどうか、あとはツアーでは触れられてませんがマルチプラットフォーム対応している点なんかがGo言語を使うかどうかの採用基準になってくるのかなと。
勢いで書いた記事になってしまいましたが、最後まで読んでいただきありがとうございました。