Go 言語の基本的な使い方
[履歴] [最終更新] (2020/09/13 00:15:36)
ここは
モノづくり総合プラットフォーム。記事の一部は有料設定にして公開できます。 詳しくはこちらをクリック📝
最近の投稿
注目の記事

概要

Go 言語に関する基本的な事項を記載します。

モジュール、パッケージ、ライブラリ、アプリケーション

一つ以上の関数をまとめたものをパッケージとして利用します。更に一つ以上のパッケージをまとめたものをモジュールとして利用します。モジュールにはライブラリとして機能するものと、アプリケーションとして機能するものがあります。

以下の例では myappmylib モジュールの二つを作り、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 パッケージを作成

エントリポイントとなるパッケージは 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

変数、fmt

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

初期化されていない変数の初期値は以下のようになります。

  • 数値 → 0
  • 真偽値 → false
  • 文字列 → ""

型のキャストは以下のようにします。

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

Go サブコマンド

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 で作成した型にメソッドを追加することができます。以下の例では AddMyInt 型のメソッドとして宣言されており、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 を用いることもできます。

その他

関連ページ