Go 言語に関する基本的な事項を記載します。
一つ以上の関数をまとめたものをパッケージとして利用します。更に一つ以上のパッケージをまとめたものをモジュールとして利用します。モジュールにはライブラリとして機能するものと、アプリケーションとして機能するものがあります。
以下の例では myapp
と mylib
モジュールの二つを作り、myapp
から mylib
を利用します。
ライブラリとして機能するモジュール
mkdir mylib
cd mylib
go mod init mylib
アプリケーションとして機能するモジュール
mkdir myapp
cd myapp
go mod init myapp
以下のようなファイルが作成されます。
$ cat go.mod
module mylib
go 1.15
mylib
ディレクトリに以下のような二つのパッケージを作成してみます。パッケージ名はモジュール名と一致している必要はありません。また、ファイル名はパッケージ名と一致している必要はありません。同じパッケージのソースコードは同じディレクトリに存在している必要があります。
mylib/mylib.go
package mylib
import "fmt"
func Hello() {
fmt.Println("hello from mylib")
}
mylib/bbb.go
package mylib
import "fmt"
func Hello2() {
fmt.Println("hello2 from mylib")
}
mylib/aaa/aaa.go
package aaa
import "fmt"
func Hello() {
fmt.Println("hello from aaa")
}
mylib/aaa/bbb.go
package aaa
import "fmt"
func Hello2() {
fmt.Println("hello2 from aaa")
}
エントリポイントとなるパッケージは main
とする必要があります。
myapp/myapp.go
package main
import (
"mylib"
"mylib/aaa"
)
func main() {
mylib.Hello()
mylib.Hello2()
aaa.Hello()
aaa.Hello2()
}
mylib
モジュールを import
するために、ここでは go.mod
を以下のように書き換えます。
myapp/go.mod
module myapp
go 1.15
replace mylib => ../mylib
スクリプト言語のように実行する場合
go run myapp.go
hello from mylib
hello2 from mylib
hello from aaa
hello2 from aaa
ビルドして実行する場合
go build
cp myapp /tmp/
cd /tmp/
./myapp
hello from mylib
hello2 from mylib
hello from aaa
hello2 from aaa
いずれの場合も、初回実行時は mylib
のチェックサムが go.mod
に追記されます。
go: found mylib in mylib v0.0.0-00010101000000-000000000000
go: found mylib/aaa in mylib v0.0.0-00010101000000-000000000000
myapp/go.mod
module myapp
go 1.15
replace mylib => ../mylib
require mylib v0.0.0-00010101000000-000000000000
var a, b int
a, b = 1, 2
初期値を指定する場合は以下のようにします。
var a, b int = 1, 2
初期値が存在する場合は型は省略可能です。Scala 等の他の言語のように型推論が行われます。
var a, b = 1, 2
fmt.Println(reflect.TypeOf(a)) //=> int
func
内であれば var
は :=
記法で省略できます。
a, b := 1, 2
初期化されていない変数の初期値は以下のようになります。
型のキャストは以下のようにします。
i := 123
f := float64(i)
定数は const
で宣言します。
const Pi = 3.14
func MyFunc(myarg string) string {
mystr := fmt.Sprintf("Hi, %v", myarg)
return mystr
}
fmt.Println(MyFunc("myarg")) //=> Hi, myarg
なお、大文字で始まる変数および関数はパッケージの外からでも利用できます。
同じ型の引数については、型をまとめて記載できます。また、返り値に名前を付けておくことで return
する変数を指定できます。ただし、これは長い関数については可読性を落とす可能性があります。
package main
import (
"fmt"
)
func MyFunc(x, y string) (a, b string) {
a = y
b = x
return
}
func main() {
fmt.Println(MyFunc("aaa", "bbb")) //=> bbb aaa
}
関数を引数にする関数
package main
import (
"fmt"
)
func MyFunc(fn func(string) string) {
fmt.Println(fn("aaa"))
}
func main() {
fn := func(str string) string {
return str + "!"
}
MyFunc(fn) //=> aaa!
}
クロージャ
package main
import (
"fmt"
)
func MyFunc() func() int {
cnt := 0
return func() int {
cnt++
return cnt
}
}
func main() {
fn := MyFunc()
fmt.Println(fn()) //=> 1
fmt.Println(fn()) //=> 2
fmt.Println(fn()) //=> 3
}
package main
import (
"fmt"
"errors"
"log"
)
func MyFunc(myarg string) (string, error) {
if myarg == "" {
return "", errors.New("myarg is empty")
}
return "ok", nil
}
func main() {
res, err := MyFunc("")
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
}
実行例
$ go run myapp.go
2020/09/09 21:53:33 myarg is empty
exit status 1
$ echo $?
1
init()
関数は、プログラム開始されてグローバル変数が初期化された後に、自動的に実行されます。
package main
import (
"fmt"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
myslice := []string{
"aaa",
"bbb",
"ccc",
}
fmt.Println(myslice[rand.Intn(len(myslice))])
}
package main
import (
"fmt"
)
func MyFunc(myargs []string) map[string]string {
mymap := make(map[string]string)
for _, myarg := range myargs {
mymap[myarg] = myarg + "!"
}
return mymap
}
func main() {
myslice := []string{
"aaa",
"bbb",
"ccc",
}
mymap := MyFunc(myslice)
fmt.Println(mymap["aaa"]) //=> aaa!
}
for ループにおけるインデックスが不要な場合は、上記のように _
を利用します。
要素の削除
m := make(map[string]int)
m["a"] = 123
delete(m, "a")
要素の存在確認
elem, ok := m["a"]
mylib/mylib.go
package mylib
import "fmt"
func Hello(myarg string) string {
return fmt.Sprintf("hello %v !", myarg)
}
mylib/mylib_test.go
package mylib
import (
"testing"
"regexp"
)
func TestHello1(t *testing.T) {
myarg := "aaa"
want := regexp.MustCompile(`\b` + myarg + `\b`)
res := Hello(myarg)
if !want.MatchString(res) {
t.Fatalf(`Hello(%q) = %q, want match for %#q`, myarg, res, want)
}
}
実行例
$ ls -l
drwxr-xr-x 2 myuser myuser 4096 2020-09-08 23:06 aaa
-rw-r--r-- 1 myuser myuser 81 2020-09-08 23:06 bbb.go
-rw-r--r-- 1 myuser myuser 22 2020-09-08 22:10 go.mod
-rw-r--r-- 1 myuser myuser 106 2020-09-09 22:38 mylib.go
-rw-r--r-- 1 myuser myuser 273 2020-09-09 22:39 mylib_test.go
$ go test
PASS
ok mylib 0.001s
main
パッケージで実行します。バイナリファイルがインストールされます。
go install
インストール場所は以下のコマンドで確認できます。
go list -f '{{.Target}}'
インストール場所は以下のコマンドで変更できます。
go env -w GOBIN=/tmp/bin
コード整形コマンドです。インデントがタブに置換されたりします。
go fmt mylib.go
新規モジュールを作る際に、最初に実行するコマンドです。
mkdir mymodule
cd mymodule
go mod init mymodule
後に公開してネットワーク経由で利用できるようにするために、ドメイン名を含めることもできます。
go mod init example.com/mymodule
for i := 0; i < 10; i++ {
fmt.Println(i)
}
終了条件を記載して他の二つを省略することで、他の言語の while のように利用できます。
i := 0
for ; i < 10; {
fmt.Println(i)
i += 1
}
;
は省略可能です。
i := 0
for i < 10 {
fmt.Println(i)
i += 1
}
無限ループは以下のようにします。
i := 0
for {
fmt.Println(i)
i += 1
if (i > 10) {
break
}
}
スライスのループは以下のようにします。
for i, v := range(s) {
fmt.Println(i)
fmt.Println(v)
}
for i := range(s) {
fmt.Println(i)
}
for _, v := range(s) {
fmt.Println(v)
}
;
で区切ることで if
の判定で利用する変数を初期化できます。これは else 内でも参照できます。
if n := 123; n < 0 {
fmt.Println("hi")
} else {
fmt.Println(n)
}
Go における switch では、他の言語における break
を省略可能です。
switch n := 123; n {
case 123:
fmt.Println(123)
case 222:
fmt.Println(222)
default:
fmt.Println(999)
}
if-else
のように利用することもできます。
n := 123
switch {
case n < 20:
fmt.Println("a")
case n < 200:
fmt.Println("b")
default:
fmt.Println("c")
}
複数の defer が存在する場合、最後によばれたものから順に実行されます。
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
実行例
$ go run myapp.go
counting
done
9
8
7
6
5
4
3
2
1
0
var p *int
i := 123
p = &i
fmt.Println(*p)
*p = 222
fmt.Println(i)
type Vertex struct {
X int
Y int
}
v := Vertex{1, 2}
v.X = 5
fmt.Println(v)
構造体へのポインタ
p := &v
C/C++ のポインタのように (*p).X
としてアクセスすることもできますが、Go では単に p.X
としてもアクセスできます。
fmt.Println((*p).X)
fmt.Println(p.X)
一部のフィールドのみ初期値を指定することもできます。
v2 := Vertex{X: 1}
スライスは、他の言語と同様に、配列への参照です。
primes := [6]int{2, 3, 5, 7, 11, 13}
s := primes[0:4]
s[0] = 17
fmt.Println(primes) //=> [17 3 5 7 11 13]
長さ 0 のスライスは nil
との比較で真を返します。
var s []int
fmt.Println(s, len(s), cap(s)) //=> [] 0 0
if s == nil {
fmt.Println("nil!")
}
以下のような記法でスライスを作成する場合、暗黙的に配列が作成されます。配列を参照するスライスの個数が 0 になると GC の対象になります。
s := []string{"a", "b", "c"}
以下のようにすると、参照先の配列に対するスライスを追加で作成できます。
s2 := s[1:]
初期値を指定せずに、配列とスライスを新規作成するためには make
を利用します。
s := make([]string, 3)
新規作成する配列の長さを、スライスの長さよりも大きくしておきたい場合は第三引数も指定します。
s := make([]string, 3, 10)
スライスの長さ、およびスライスから見える配列の長さは以下のように取得できます。
len(s)
cap(s)
スライスのコピーは以下のようにします。
s := []string{"a", "b", "c"}
t := make([]string, 3, 10)
copy(t, s)
fmt.Println(t) //=> [a b c]
スライスへの値の追加は以下のようにします。参照先の配列の長さが不足する場合はおおよそ二倍の長さの配列が新規に作成されます。
s := []string{"a", "b", "c"}
fmt.Println(len(s)) //=> 3
fmt.Println(cap(s)) //=> 3
s = append(s, "d", "e")
fmt.Println(len(s)) //=> 5
fmt.Println(cap(s)) //=> 6
スライスへの、別のスライスの値の追加は以下のような記法になります。
s := []string{"a", "b", "c"}
t := []string{"d", "e"}
s = append(s, t...)
スライスが参照する先の配列が大きく、実際はそのうちの一部しか利用しない場合は、新規に配列を作成することでメモリ使用量を節約できます。
package main
import (
"fmt"
)
func MyFunc() []string {
s := make([]string, 1, 1024)
s[0] = "aaa"
t := make([]string, len(s))
copy(t, s)
return t
}
func main() {
s := MyFunc()
fmt.Println(len(s)) //=> 1
fmt.Println(cap(s)) //=> 1
}
Go にはクラスが存在しませんが、新規に type
で作成した型にメソッドを追加することができます。以下の例では Add
が MyInt
型のメソッドとして宣言されており、Add
メソッドは i MyInt
をレシーバとして持ちます。
package main
import (
"fmt"
)
type MyInt int
func (i MyInt) Add(j MyInt) MyInt {
return i + j
}
func main() {
var n MyInt = 1
fmt.Println(n.Add(10)) //=> 11
}
他の言語におけるメンバ変数の値を変更するようなメソッドを作成するためには、メソッドのレシーバをポインタにします。
package main
import (
"fmt"
)
type MyStruct struct {
X int
}
func (o *MyStruct) Increment() {
o.X += 1
}
func main() {
o := MyStruct{}
o.Increment()
o.Increment()
fmt.Println(o) //=> {2}
}
他の言語において、インターフェースはクラスが implements
キーワードで実装します。Go では型にメソッドを追加することでインタフェースを実装します。
package main
import (
"fmt"
)
type MyInterface interface {
Method1(int) int
Method2()
}
type MyInt int
func (i MyInt) Method1(j int) int {
return int(i) + j
}
func (i MyInt) Method2() {
fmt.Println(i)
}
func main() {
var i MyInterface = MyInt(123)
fmt.Println(i.Method1(1)) //=> 124
i.Method2() //=> 123
}
インターフェースを実装するメソッドのレシーバが nil
となる場合
package main
import (
"fmt"
)
type MyInterface interface {
MyMethod()
}
type MyStruct struct {
X int
}
func (o *MyStruct) MyMethod() {
if o == nil {
fmt.Println("<nil>!!")
return
}
fmt.Println(o.X)
}
func main() {
var i MyInterface
var o *MyStruct
fmt.Println(o == nil) //=> true
i = o
i.MyMethod() //=> <nil>!!
i = &MyStruct{123}
i.MyMethod() //=> 123
}
実装を要求するメソッドの個数が 0 であるようなインターフェースの型は interface{}
です。任意の型の値を扱う必要がある場合に利用します。
package main
import (
"fmt"
)
func main() {
var i interface{}
i = 123
fmt.Println(i)
}
インターフェースが、実際にはどの型の値であるかを判定するためには以下のようにします。
package main
import (
"fmt"
)
func main() {
var i interface{} = "hello"
s, ok := i.(string)
fmt.Println(s) //=> hello
fmt.Println(ok) //=> true
}
型によって処理を分岐するためには以下のようにします。
package main
import (
"fmt"
)
func main() {
var i interface{} = "hello"
switch v := i.(type) {
case int:
fmt.Println("int", v)
case string:
fmt.Println("string", v)
default:
fmt.Println("unknown")
}
}
fmt
パッケージにある Stringer
インタフェースは String()
メソッドを実装することを要求しています。新規に定義した型に String()
メソッドを実装することで、fmt
パッケージなどが String()
メソッドをよんだときの処理を実装できます。
package main
import (
"fmt"
)
type MyString string
func (s MyString) String() string {
return string(s) + "!"
}
func main() {
var s MyString = "hello"
fmt.Println(s) //=> hello!
}
エラー処理に必要となる型を定義するためには、新規に型を定義して error
インタフェースが要求する Error() string
メソッドを実装します。
package main
import (
"fmt"
)
type MyError struct {
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("because of %v", e.What)
}
func MyFunc() error {
ok := false
if !ok {
return &MyError{"failed"}
}
return nil
}
func main() {
err := MyFunc()
if err != nil {
fmt.Println(err)
}
}
Java 等のスレッドと同様に、Go のスレッドは複数のコアを利用して動作します。Python の場合と区別します。チャネルとよばれる機能を用いてスレッド間の通信が可能です。
package main
import (
"fmt"
)
func MyFunc(c chan string) {
c <- "hello"
}
func main() {
c := make(chan string)
go MyFunc(c)
res := <-c
fmt.Println(res)
}
チャネルのバッファ数は既定では 1 です。これを大きくするためには make
の第 2 引数を指定します。
チャネル内のデータが受け手に処理されずにバッファの限界まで溜まった場合、他の言語における、例えば Akka Stream のように、チャネルへのデータ送信は一時停止されます。
package main
import (
"fmt"
"time"
)
func MyFunc(c chan string) {
fmt.Println("hi1")
c <- "hello1"
fmt.Println("hi2")
c <- "hello2"
}
func main() {
c := make(chan string)
// c := make(chan string, 2)
go MyFunc(c)
time.Sleep(3 * time.Second)
res := <-c
fmt.Println(res)
res2 := <-c
fmt.Println(res2)
}
バッファ数 1 の場合
$ go run myapp.go
hi1
hello1
hi2
hello2
バッファ数 2 の場合
$ go run myapp.go
hi1
hi2
hello1
hello2
データの送信が終了したことを受信側が知る必要がある場合は close
を利用します。
package main
import (
"fmt"
)
func MyFunc(c chan int) {
for i := 0; i < 5; i++ {
c<- i
}
close(c)
}
func main() {
c := make(chan int)
go MyFunc(c)
for i := range c {
fmt.Println(i)
}
}
上記ループ処理は以下のように書いても同様です。
for {
i, ok := <-c
if !ok {
break
}
fmt.Println(i)
}
select-case
を利用すると、現在のチャネルの状態で実行可能な処理から、一つがランダムに選ばれて実行されます。default
の指定は必須ではありません。
package main
import (
"fmt"
"time"
)
func SendData(cData chan int, cQuit chan bool) {
i := 0
for {
select {
case cData <- i:
fmt.Println("sent", i)
case <-cQuit:
fmt.Println("quit")
return
default:
i++
}
}
}
func ReceiveData(cData chan int, cQuit chan bool) {
for i := 0; i < 5; i++ {
fmt.Println(<-cData)
}
cQuit <- true
}
func main() {
cData := make(chan int)
cQuit := make(chan bool)
go SendData(cData, cQuit)
go ReceiveData(cData, cQuit)
time.Sleep(time.Second)
}
実行例
$ go run myapp.go
sent 0
0
sent 1387
1387
sent 2376
2376
sent 3685
3685
sent 4516
4516
quit
Goroutine で並列処理を行う場合、Scala の Akka アクターのようにスレッド間通信を行いたい場合は上述のチャネルを用います。そうではなく、共通リソースを複数のスレッドから扱いたい場合は、他の言語と同様に Lock を用います。
package main
import (
"fmt"
"time"
"sync"
)
func main() {
cnt := 0
mux := sync.Mutex{}
for i := 0; i < 1000; i++ {
go func() {
mux.Lock()
// defer mux.Unlock()
cnt++
mux.Unlock()
}()
}
time.Sleep(time.Second)
mux.Lock()
fmt.Println(cnt)
mux.Unlock()
}
実行例
$ go run myapp.go
1000
排他制御を行わない場合は以下のようになります。
$ go run myapp.go
955
Unlock
を処理の最後に記述する以外の方法として、前述の defer
を用いることもできます。