3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

A Tour of Go やってみた

Last updated at Posted at 2025-12-02

はじめに

今年は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)
}

処理の流れ

  1. cから値を10回受信してコンソール表示し、quitに値を送信する処理をゴルーチンでサブスレッドに登録。
  2. fibonacciを実行
  3. selectでquitは空だから選択されず、cは空なのでxの送信が選択される。
  4. cに送信されたので1のゴルーチンでcから受信してコンソール表示される。
  5. 3と4を10回繰り返す。
  6. 10回ループを抜けてcからの受信がなくなってquitに0が送信される。
  7. selectでcは空じゃないので送信が選択されず、quitは空じゃないので受信が選択される。
  8. "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言語を使うかどうかの採用基準になってくるのかなと。

勢いで書いた記事になってしまいましたが、最後まで読んでいただきありがとうございました。

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?