目次
Goとは
Go は、Python のように手軽に使えて、Python よりも高速にかつ安全に動くことを意図された言語のようだが、妙に古臭いというか、懐かしい感じのする言語である。“Go” だと一般的すぎるので、“Go 言語” とか “Golang” と表記されることがある。コンパイラ言語である。
インストール
こちら からダウンロードして設置する。
$ tar xvzf go1.26.5.linux-amd64.tar.gz -C ~/opt
~/.bashrc
export PATH=$PATH:~/opt/go/bin
プログラムの構造と実行
プログラムの基本的な構造は以下のとおりである。
hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
ファイルの拡張子は .go である。package の行はおまじないである。import の行はパッケージのインポートである。それに続いて main 関数が定義されている。ここがプログラムの入口になる。コードは少しC言語に似ているが、文末のセミコロンはいらない。というか、内部で自動的に追加される。{ は関数名の下には置けない。というのも、改行でセミコロンが追加されるためである。インデントは公式には Tab である(空白でも動くが)。整形用のコマンドがあるので、あまり細かいことは気にしなくてよい。
$ go fmt hello.go
go vet で問題のある箇所を指摘してくれる。
$ go vet hello.go
実行は、go run で行う。
$ go run hello.go
Hello, world!
go build で実行ファイルができる。
$ go build hello.go
$ ./hello
文
文はセミコロン(;)で終わる。といっても、ある規則で勝手に挿入されるので、基本的には自分では書かない。JavaScript のように、自動で入るには入るが危なっかしいので必ず入れる、ということもなく、入れないのが普通のようである。ただ、変なところで改行したら罠にハマりうるのは JavaScript と同様である。
一見、以下のように書けそうではある。
func main()
{
fmt.Println("Hello, world!")
}
だが、次のようなエラーが出る。
./hello.go:6:1: syntax error: unexpected semicolon or newline before {
これは、")" のあとに改行が入るとセミコロンが挿入されるためである。変な感じがするかもしれないが、スタイルの揺れを制限しているのである。
コメント
C/C++ などと同様、/* ... */ や // ... でコメントを入れることができる。
/* 文字列を表示 */
fmt.Println("Hello, world!")
変数の定義
変数の定義はつぎのようにする。
var a = 1
型を直接指定することもできる。
var a int
a = 1
あるいは
var a int = 1
Go では型を後につけるのが特徴。
定数にする場合は、const を使う。
const a int = 1
短縮代入 := も使える。
a := 1
これは var a = 1 と同じ意味。左辺の型は推論される。
データ型
Go においては、すべてのデータに明確な型がある。型を調べるには fmt.Printf の %T フォーマットを使える。
fmt.Printf("%T\n", a)
整数型
整数型には int64、uint64 などがある。uint のほうは符号なしである。64 はビット数を表し、8、16、32、64 とある。単に int と書くと、CPU に応じて int64 あるいは int32 となる。uint も同様。値を直接指定する場合(リテラル)の整数型は int である。
var a = 1
byte 型というものがあるが、int8 の別名である。
C/C++ と似ているが、Go では int のサイズがプラットフォーム依存である点に注意。C/C++ では int は通常 32 ビットだが、Go では 64 ビット環境では int64 になる。
浮動小数点数型
浮動小数点数型は、float64、float32 がある。リテラルの型は float64 である。
var a = 1.0
型変換は、型の名前で行える。
a = float64(1)
C/C++ と異なり、暗黙の型変換はほとんど行わない。次のようにするとエラーになる。
var a = 1
a = 1.2 // error
明示的なキャストが必要。
複素数型
複素数型として complex128、complex64 がある。前者が float64、後者が float32 を実部と虚部の表現に使っている。虚数を表すには i を用いる。
var a = 1.0 + 2.0i
fmt.Println(a) // (1+2i)
あるいは、以下のようにしてもよい。
var a = complex128(1.0, 2.0)
real()、imag() で実部と虚部が得られる。
fmt.Println(real(a)) // 1
fmt.Println(imag(a)) // 2
文字列型
文字列は string 型で表す。"..." あるいは '...' ではなく、"..." のみ。
var s string = "Hello"
文字列は不変(immutable)である。一度作成された文字列の内容は変更できない。C の char 配列とは根本的に異なる。
各文字のアクセス
文字列の各文字にアクセスするには、添字を使う(0 から始まる)。
var s string = "Hello, world."
fmt.Printf("%c\n", s[0]) // H
各文字は変更できない。
s[0] = 'h' // error
添字には負の値は使えない。末尾要素にアクセスするには s[len(s)-1] を使う。
var s string = "Hello, world."
fmt.Println(s[len(s)-1]) // .
Pythonの s[-1] のような負のインデックスはGoにはない。
部分文字列にアクセスすることもできる。1:4 なら 2 文字目から 4 文字目になる(1:4 は添字としては 1, 2, 3 を意味する)。これをスライス表記という。
var s string = "Hello, world."
fmt.Println(s[1:4]) // ell
文字列の長さ
文字列の長さを知るには len() を用いる。
var s string = "Hello, world."
fmt.Println(len(s)) // 13
文字列の連結
文字列の連結には + を用いる。
var s string = "Hello" + ", " + "world" + "."
変数の埋め込み
fmt.Sprintf で変数を埋め込むことができる。
var name string = "world"
var s string = fmt.Sprintf("Hello, %s.", name)
三重引用符に相当するもの
Go には Python の """...""" に相当するものはないが、バッククォート `...` で囲むと、エスケープなしの文字列(raw string)になる。
var s string = `Hello,
world.`
改行を含む文字列をそのまま書ける。
論理型
bool 型があり、true と false を取る。
var b bool = true
fmt.Printf("%T\n", b) // bool
C/C++ では 0 と非 0 が真偽値として使われるが、Go では bool 型が明示的に必要。
ポインタ
ポインタの基本
Go にもポインタがある。C/C++ と同様に & でアドレスを取得し、* で間接参照する。
x := 42
p := &x // p は x のアドレスを指す
fmt.Println(*p) // 42
*p = 100 // x の値を変更
fmt.Println(x) // 100
C/C++ と似ているが、Go のポインタはNULL がない。ゼロ値は nil。
var p *int
fmt.Println(p) // <nil>
C/C++との違い
C/C++ ではポインタ演算(p++ など)が可能だが、Go ではポインタ演算はサポートされていない。
また、Go のガベージコレクションにより、ポインタが指す先が寿命を越えることはない(ポインタが指す先が不要になれば GC により回収される)。C/C++ のような「ポインタが指す先が解放された後にアクセスする」ようなバグは起こりにくい。
配列とスライス
配列
var a [3]int
a[0] = 1
a[1] = 2
a[2] = 3
C/C++ の配列に似ているが、Go の配列は値型である。配列を関数に渡すとコピーされる。C/C++ では配列はポインタとして振る舞うが、Go ではそうではない。
同じ型の配列を初期化するには:
var a [3]int = [3]int{1, 2, 3}
長さだけを指定してゼロ値で埋めることもできる。
var a [3]int = [3]int{}
末尾要素のアクセス
Goには負のインデックスがない。末尾要素にアクセスするには a[len(a)-1] を使う。
スライス
配列に対して範囲指定(a[1:3])すると、スライスが得られる。スライスは配列の連続した範囲を指すビューで、要素の追加・削除ができる。内部では配列へのポインタ・長さ・容量の3つの情報を持つ。スライスを定義するには、[]型名で表す。
var a [5]int = [5]int{1, 2, 3, 4, 5}
var s []int = a[1:3] // スライス
fmt.Println(s) // [2 3]
a[2:]、a[:3]、a[:]のような書き方もできる。
Python の変数がオブジェクトの参照を持つのと違って、Go の変数はオブジェクトそのものを持つ箱である。スライスで配列のビューを実現することによって、Python のリスト型変数(参照)と同じ振る舞いを実現していると考えることができる。
スライスの内部構造
スライス自体はメモリを持っていない。あくまで配列へのポインタ+長さ・容量のヘッダで、実データは配列にある。
スライス s → [ ptr→配列本体, len=3, cap=5 ]
make でスライスを作成する際も、裏で配列が確保されている。
var s []int = make([]int, 3, 5) // 長さ3、容量5
s[0] = 1
s[1] = 2
s[2] = 3
第3引数(容量)は省略可能。容量を省略すると容量=長さになる。
スライスのスライス(部分スライス)も扱える。
var s []int = []int{1, 2, 3, 4, 5}
fmt.Println(s[1:3]) // [2 3]
注意: スライスの値を変更すると、もとの配列や同じ配列を参照しているスライスの値も変わる(Python のリスト操作ではおなじみの現象)。
a := [5]int{1, 2, 3, 4, 5}
s := a[0:3] // [1, 2, 3]
s[0] = 99 // a[0]も変わる
append
スライスは可変長なので、append で要素を追加できる。
s := append(s, 4)
fmt.Println(s) // [1 2 3 4]
複数の要素を追加することもできる。
s = append(s, 5, 6, 7)
append で容量を超えると新しい配列が割り当てられる。元の配列とのビュー関係は切れるので、同じ配列を共有しているスライスをappendする場合には気をつける必要がある。
a := make([]int, 3) // cap=3
s1 := a[0:3] // [0, 0, 0]
s2 := a[1:3] // [0, 0]
s1 = append(s1, 99) // cap超過で新配列が確保される
s2[0] = 88 // s2はaを参照
fmt.Println(s1) // [0, 0, 0, 99] ← 新配列
fmt.Println(s2) // [88, 0] ← aが変更されている
fmt.Println(a) // [0, 88, 0]
コピー
スライスをコピーするには copy を使う。
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
Python の list.copy() やスライス s[:] に相当する。
マップ
mapの定義
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println(m["a"]) // 1
Python の辞書(dict)に相当する。
mapの操作
存在しないキーにアクセスするとゼロ値が返る。
fmt.Println(m["c"]) // 0
キーが存在するか確認するには、代入付きのアクセスを使う。
v, ok := m["c"]
fmt.Println(v, ok) // 0 false
Python で dict.get(key, default) のようにデフォルト値を指定する必要があるが、Go ではこのパターンが標準。
削除は delete を使う。
delete(m, "a")
構造体
structの定義
type Person struct {
name string
age int
}
p := Person{name: "Alice", age: 30}
fmt.Println(p.name) // Alice
C/C++ の struct に似ているが、Go の構造体にはメソッドを定義できる。
フィールドアクセス
Go では . でフィールドにアクセスする
p := Person{name: "Alice", age: 30}
fmt.Println(p.name) // Alice
ポインタ経由の場合も . でアクセスできる((*p).name ではなく p.name と書ける)。
式と演算子
算術演算子
整数・浮動小数点数については、以下のような演算子が使える。
| 演算子 |
例 |
意味 |
+ |
a + b |
加算 |
- |
a - b |
減算 |
* |
a * b |
乗算 |
/ |
a / b |
除算 |
% |
a % b |
剰余 |
- |
-a |
負符号 |
** のような累乗演算子はない。math.Pow を使う。
import "math"
fmt.Println(math.Pow(2, 10)) // 1024
整数の除算は整数値になる(小数部は切り捨てられる)。
fmt.Println(7 / 2) // 3
小数で除算するには、明示的に浮動小数点数に変換する。
fmt.Println(float64(7) / float64(2)) // 3.5
Pythonの // のような整数除算は、Goでは整数型同士の / に相当する。
代入演算子
+=、-=、*=、/=、%= などの代入演算子がある。
i := 0
i += 1 // i = i + 1 と同じ
i -= 2 // i = i - 2 と同じ
i *= 3 // i = i * 3 と同じ
インクリメント・デクリメント
Goには(Pythonと違って) ++ と -- がある。
i := 0
i++ // i = 1
i-- // i = 0
前置も可能。
fmt.Println(++i) // iをインクリメントしてから出力
ただし、式の一部としては使えない。
fmt.Println(i++) // error
これは「文としてのみ有効」というGoの仕様である。Pythonの i += 1 に相当するが、Goでは += ではなく ++ も使える。
ビット演算子
| 演算子 |
意味 |
& |
AND |
\| |
OR |
^ |
XOR |
&^ |
AND NOT |
<< |
左シフト |
>> |
右シフト |
fmt.Println(1 & 1) // 1
fmt.Println(1 | 2) // 3
fmt.Println(1 ^ 2) // 3
fmt.Println(1 << 2) // 4 (1 * 2^2)
fmt.Println(4 >> 1) // 2 (4 / 2^1)
&^(AND NOT)は、右辺のビットを立てた部分を左辺から取り除く。
fmt.Println(7 &^ 1) // 6 (111 &^ 001 = 110)
比較演算子
| 演算子 |
意味 |
== |
等しい |
!= |
等しくない |
< |
より小さい |
<= |
以下 |
> |
より大きい |
>= |
以上 |
fmt.Println(1 == 1) // true
fmt.Println(1 != 2) // true
fmt.Println(1 < 2) // true
Pythonとの違い: Goでは比較演算子をつなげられない。
// Python: 0 <= x <= 1 は有効
// Go: これはエラー
0 <= x && x <= 1 // こう書く
論理演算子
| 演算子 |
意味 |
&& |
AND |
\|\| |
OR |
! |
NOT |
if x >= 0 && y <= 5 {
// ...
}
if !found {
// ...
}
短絡評価(ショートサーキット)が行われる。&& で左が false なら右は評価されない。|| で左が true なら右は評価されない。
// x が 0 より小さい場合、y は評価されない
if x >= 0 && y <= 5 {
// ...
}
演算子の優先順位
優先順位は以下のとおり(上が高い)。
| 優先度 |
演算子 |
| 1 |
++ -- |
| 2 |
* / % << >> & &^ |
| 3 |
+ - \| ^ |
| 4 |
== != < <= > >= |
| 5 |
&& |
| 6 |
\|\| |
() で囲むことで優先順位を変更できる。
fmt.Println(2 + 3 * 4) // 14
fmt.Println((2 + 3) * 4) // 20
制御文
if文
if x > 0 {
fmt.Println("positive")
} else if x < 0 {
fmt.Println("negative")
} else {
fmt.Println("zero")
}
C/C++ と似ているが、条件式が必ず bool 型である点に注意。また、{ は改行してはいけない。
for文
Go には while や do-while はない。すべて for で行う。
// C の for ループに相当
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while に相当
for i < 10 {
fmt.Println(i)
i++
}
// 無限ループ
for {
// ...
}
range
配列やスライスの各要素をイテレートするには range を使う。
nums := []int{1, 2, 3, 4, 5}
for i, v := range nums {
fmt.Println(i, v)
}
Python の for i, v in enumerate(nums): に似ている。インデックスだけ欲しい場合は次のようにする。
for i := range nums {
fmt.Println(i)
}
値だけ欲しい場合は _ でインデックスを無視できる。
for _, v := range nums {
fmt.Println(v)
}
マップでも使える。
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
fmt.Println(k, v)
}
switch文
switch x {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}
C/C++ の switch と似ているが、各 case で自動的に break されるのが大きな違いである。
明示的にフォールスルーしたい場合は fallthrough を使う。
switch x {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("two")
}
関数
関数の定義
func add(a int, b int) int {
return a + b
}
C/C++ と似ているが、型名が変数名の後ろにくるのが特徴。
同じ型の引数が連続している場合、最後の型名だけを共有できる。
func add(a, b int) int {
return a + b
}
Go では関数内に関数を定義することができない(無名関数で似たようなことはできるが)。
複数戻り値
Go では複数の値を戻り値にできる。
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
q, r := divmod(10, 3)
fmt.Println(q, r) // 3 1
}
関数呼び出し
関数は () で呼び出す。
result := add(1, 2)
戻り値が複数の場合は、すべて受け取るか、_ で無視できる。
q, _ := divmod(10, 3) // r を無視
_, r := divmod(10, 3) // q を無視
引数渡し
Goの関数引数はすべて値渡しである。引数に渡された値はコピーされる。
func inc(x int) {
x++ // コピーされたものを変えるだけ
}
func main() {
v := 1
inc(v)
fmt.Println(v) // 1 ← 変わっていない
}
実引数の値を変更したい場合は、ポインタで渡す。
func inc(x *int) {
*x++ // ポインタ経由で実物を変える
}
func main() {
v := 1
inc(&v)
fmt.Println(v) // 2 ← 変わっている
}
Pythonではミュータブルなオブジェクト(リストなど)を渡すと(参照なので)関数内で変更できるが、Goでは明示的にポインタで渡す必要がある。
可変長引数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
再帰呼び出し
関数の再帰呼び出しはふつうにできる。
func fibonacci(n int) int {
if n == 0 {
return 0
} else if n == 1 {
return 1
} else {
return fibonacci(n - 2) + fibonacci(n - 1)
}
}
無名関数(ラムダ式)
Goには無名関数(ラムダ式)がある。関数を変数に代入したり、引数に渡したりできる。
f := func() string {
return "hello"
}
fmt.Println(f()) // hello
関数も値なので、変数に代入できる。引数や戻り値にもできる。
即時実行
result := func() int {
return 42
}()
fmt.Println(result) // 42
クロージャ
外の変数にアクセスできる(クロージャ)。
i := 0
inc := func() {
i++ // 外の変数にアクセスできる
}
inc()
fmt.Println(i) // 1
呼ぶたびに 1 増えた数値を返す関数を定義したい。
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
そのためには、ローカルの環境に変数を閉じ込める形で関数を定義すればよい。この仕組みをクロージャという。目的の関数は、次のように実現できる。
func setup() func() int {
x := 0
f := func() int {
x++
return x
}
return f
}
func main() {
f := setup()
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
}
関数内関数
Goで関数内に関数を定義するには、無名関数として定義する必要がある。
func main() {
f := func() {
fmt.Println("hello")
}
f()
}
メソッド
Goにはクラスがないが、構造体にメソッドを定義できる。
メソッドの定義
メソッドはレシーバー(受信者)を持つ関数である。レシーバーはメソッドが呼び出される対象。
type Dog struct{}
func (d Dog) Speak() string { // Dog へのメソッド
return "wan"
}
func main() {
d := Dog{}
d.Speak() // "wan"
}
func (d Dog) がレシーバーの宣言。d を通じて構造体のフィールドにアクセスできる。
ポインタレシーバー
レシーバーをポインタにすると、構造体を変更できる。
type Counter struct {
n int
}
func (c *Counter) Inc() { // ポインタレシーバー
c.n++ // 実物を変更できる
}
func main() {
c := Counter{n: 0}
c.Inc()
fmt.Println(c.n) // 1
}
値レシーバーだとコピーされたものを変えるだけなので、実物を変更したい場合はポインタレシーバーが必要。
インターフェース
Goのインターフェースは、Javaのインターフェースに近い概念だが、明示的な実装宣言がないのが大きな違い。
インターフェースの定義
インターフェースはメソッドシグネチャの集合体を定義する。
type Speaker interface {
Speak() string
}
これは「Speak() string を持つもの」という契約を定義している。
Javaのインターフェースとの違い
Javaではクラスがインターフェースを明示的に実装する。
// Java
interface Speaker { // インターフェースの定義
String speak();
}
class Dog implements Speaker { // 明示的に実装
public String speak() {
return "wan";
}
}
Goでは明示的な実装宣言がない。構造体が必要なメソッドを持っていれば自動的にインターフェースを満たす(暗黙的実装)。
type Speaker interface {
Speak() string
}
type Dog struct{} // 構造体の定義だけ
func (d Dog) Speak() string { // メソッドを実装
return "wan"
}
func main() {
var s Speaker = Dog{} // Dog は自動的に Speaker を満たす
fmt.Println(s.Speak()) // "wan"
}
Javaの class Dog implements Speaker に対応する記述がGoにはない。Dogが Speak() メソッドを持っているだけで、自動的に Speaker を満たすとみなされる。要するにダッグタイピング(アヒルっぽいく振る舞うならアヒルとみなす)である。
型アサーション
インターフェースから元の型を取り出すには型アサーションを使う。
var s Speaker = Dog{}
d := s.(Dog) // Dog 型として取り出す
fmt.Println(d)
この方法だと、失敗するとパニックになる。
type Cat struct{}
func (c Cat) Speak() string {
return "nyan"
}
func main() {
var s Speaker = Cat{}
fmt.Println(s.Speak()) // "nyan"
d := s.(Dog)
fmt.Println(d)
}
panic: interface conversion: main.Speaker is main.Cat, not main.Dog
安全に取り出すにはコンマ付きの構文を使う。
d, ok := s.(Dog)
if ok {
fmt.Println(d)
}
Pythonでいう isinstance(s, Dog) に相当するが、Goでは型アサーションで値自体を取り出せる点が異なる。
エラー処理
Go には例外の仕組みがない。C/C++ の戻り値でのエラーコードに近いが、Go ではエラーが値として扱われる。
errorインターフェース
Go の error はインターフェースである。
type error interface {
Error() string
}
errors.New、fmt.Errorf
errors.New で単純なエラーを作成できる。
import "errors"
err := errors.New("division by zero")
fmt.Errorf でフォーマット付きのエラーも作れる。
import "fmt"
err := fmt.Errorf("cannot divide %d by %d", a, b)
カスタムエラー
独自のエラー型を作ることができる。
type DivisionError struct {
msg string
}
func (e DivisionError) Error() string {
return e.msg
}
Error() string メソッドを持っていれば、それは error インターフェースを満たす。
関数からのエラー返し
Goでは、関数でエラーが発生した場合はエラーを戻り値として返すのが定石である。
func div(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := div(10, 0)
if err != nil {
fmt.Println("error:", err) // "error: division by zero"
return
}
fmt.Println(result)
}
呼び出し元では if err != nil でエラーチェックを行う。
エラー処理のパターン
result, err := div(10, 0)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(result)
C/C++ の if (err != 0) に似ているが、Go ではエラーが値として扱われるため、if err != nil でチェックする。
パッケージ
インポート
import (
"fmt"
"math"
)
Python の import に似ているが、Go ではパッケージ内の名前(変数、関数、型など)が大文字始まりでないと外部から使えない。
ここで重要なのは、そのパッケージ内の名前が大文字始まりかどうかである。
package fmt // パッケージ名は小文字
func Println(a ...interface{}) { ... } // Println は大文字始まり → エクスポートされる
func println(a ...interface{}) { ... } // println は小文字始まり → エクスポートされない
エクスポート
Go のパッケージは、大文字始まりの名前しか外部に公開されない。変数、関数、型、構造体フィールドなどすべてに適用される。
// math.go
const Pi = 3.14159 // 公開
const pi = 3.14 // 非公開
func Sqrt(x float64) float64 { // 公開
return math.Sqrt(x)
}
func sqrt(x float64) float64 { // 非公開
return math.Sqrt(x)
}
Python では __name のように明示的にプライベートを示す必要があるが、Go は大文字=公開、小文字=非公開という大雑把なルールである。
構造体名とフィールド
構造体名も、大文字始まりでないと外部から使えない。
// mymath.go
type Person struct { // 大文字始まり → 外部から使える
Name string // 大文字始まり → 外部からアクセスできる
age int // 小文字始まり → 外部からアクセスできない
}
type person struct { // 小文字始まり → 外部から使えない
name string
age int
}
// main.go
import "mymath"
p := mymath.Person{Name: "Alice", age: 30}
fmt.Println(p.Name) // 使える
fmt.Println(p.age) // エラー: mymath.Person.age は非公開
mainパッケージ
main パッケージの main 関数がプログラムの入口になる。
package main
func main() {
// ...
}
ゴルーチンとチャンネル
ゴルーチンの基本
Go にはゴルーチン(goroutine)という軽量なスレッドがある。go キーワードで起動する。
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
go say("hello") // 新しいゴルーチンで実行
say("world") // 現在のゴルーチンで実行
}
Python の threading.Thread に似ているが、Go のゴルーチンははるかに軽量である。
チャンネル
ゴルーチン間の通信にはチャンネル(channel)を使う。
ch := make(chan string) // バッファなしチャンネルの作成
// ゴルーチンA:データを送信
go func() {
ch <- "hello" // チャンネルにデータを入れる(受信されるまで待機)
}()
// ゴルーチンB:データを受信
msg := <-ch // チャンネルからデータを取り出す(受信できるまで待機)
fmt.Println(msg) // hello
容量を指定しない場合は、バッファなしチャンネルになる。この場合、送信と受信が同期される(お互いが待機状態になる)。
バッファありチャンネルも作れる。
ch := make(chan string, 2) // 容量2のバッファ
ch <- "a"
ch <- "b"
ch <- "c" // ブロックされる(待機状態になる)
バッファありの場合、容量いっぱいになるまで送信はブロックされない。Python の queue.Queue に似ているが、Go のチャンネルはより低レベルで柔軟。
Goモジュール
go.mod
Go 1.11 以降、モジュールシステムが導入された。go.mod ファイルで依存関係を管理する。
$ go mod init myproject
依存管理
依存の追加は go get で行う。
$ go get github.com/some/package
Python の pip install に相当するが、Go のモジュールはバージョン管理がより厳密。
テスト
Go には標準のテストフレームワーク testing がある。これを用いるには go.mod が必要。
// hello_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
result := add(1, 2)
if result != 3 {
t.Errorf("expected 3, got %d", result)
}
}
Go のテストファイルは _test.go とする。
テストの実行は go test で行う。
$ go test
|