Go言語開発メモ

Goインストール

最新バージョンは https://go.dev/dl/ で確認してからダウンロードします。以下のコマンドは 2026年6月時点での最新バージョン(go1.26.4)を例として示しています。

# 最新バージョンを https://go.dev/dl/ で確認してからダウンロード
wget https://go.dev/dl/go1.26.4.linux-amd64.tar.gz

既存の Go を削除してから展開します(上書きは破損の恐れがあるため必ず削除してから行う)。

sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.26.4.linux-amd64.tar.gz

PATH に追加します(~/.bashrc または ~/.profile に追記)。

export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:$(go env GOPATH)/bin

source ~/.bashrc

インストールが完了したら、バージョンを確認します。

go version

参考にしたページ

初期設定のベストプラクティス

Go 1.11 以降は go.mod によるモジュール管理が標準となり、GOPATH の役割が変化しました。

GOPATH の現在の役割

go env -w で設定する(推奨)

環境変数は go env -w コマンドで設定ファイルに永続保存するのが推奨です。 シェルの設定ファイルに直接書く必要はありません。

# 設定ファイルの場所を確認
go env GOENV

# モジュールプロキシ(デフォルト:proxy.golang.org)
go env -w GOPROXY=https://proxy.golang.org,direct

# 社内プライベートリポジトリの設定
go env -w GOPRIVATE=gitlab.mycompany.com,github.com/my-org

# 設定を削除
go env -u GOPROXY

主な環境変数

変数 デフォルト 説明
GOPROXY https://proxy.golang.org,direct モジュール取得先プロキシ
GOPRIVATE (未設定) プロキシ・チェックサムDBをバイパスするパターン
GOSUMDB sum.golang.org モジュールの改ざん検証DB
GOBIN $GOPATH/bin go install バイナリの出力先

プロキシとチェックサムDB

Go の特徴的な機能

goroutine と channel

goroutine は初期スタック約 2KB の軽量スレッドで、数万〜数百万単位で起動できます。 channel で goroutine 間の通信を行い、select 文で複数 channel を同時に待機できます。

package main

import "fmt"

func sum(s []int, c chan int) {
    total := 0
    for _, v := range s {
        total += v
    }
    c <- total
}

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
    fmt.Println(x, y, x+y)
}

interface(構造的型付け)

Go の interface は構造的型付けを採用しており、implements キーワードは不要です。 メソッドを持っていれば自動的に interface を実装していると見なされます。

type Animal interface {
    Sound() string
    Name()  string
}

type Dog struct{}
func (d Dog) Sound() string { return "Woof" }
func (d Dog) Name()  string { return "Dog" }

// implements キーワード不要。メソッドを持っていれば自動的に実装扱い
func Describe(a Animal) {
    fmt.Printf("%s says %s\n", a.Name(), a.Sound())
}

defer / panic / recover

// defer: 関数終了時に必ず実行(LIFO 順)
func readFile(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close() // 忘れずに閉じる

    // ...
    return nil
}

// panic / recover: 例外的なエラー処理
func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    return a / b, nil
}

error handling

Go のエラーは戻り値として返します。Go 1.13 以降は %w でエラーをラップできます。

var ErrNotFound = errors.New("not found")

// エラーのラップ(Go 1.13+)
func findUser(id int) (*User, error) {
    user, err := db.Find(id)
    if err != nil {
        return nil, fmt.Errorf("findUser(%d): %w", id, err)
    }
    return user, nil
}

// エラーの検査
if errors.Is(err, ErrNotFound) { /* センチネルエラーとの一致 */ }
var ve *ValidationError
if errors.As(err, &ve) { /* エラーチェーン内の型を取り出す */ }

generics(Go 1.18+)

func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}

fmt.Println(Min(3, 5))       // 3
fmt.Println(Min("a", "b"))   // a

ゼロ値

変数宣言のみで型に応じたゼロ値が自動的に代入されます。 int は 0、bool は false、string は ""、 ポインタ・スライス・マップ・チャネルは nil です。

複数戻り値

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)

Goプロジェクトの作成

プロジェクトディレクトリを作成します。

mkdir -p $GOPATH/github.com/naughty-ghost/hello

プロジェクトディレクトリに移動します。

cd $GOPATH/github.com/naughty-ghost/hello

main.goファイルを作成します。

touch main.go

main.goファイルに以下のコードを記述します。

package main

import "fmt"

func main() {
  fmt.Println("Hello, World!")
}

main.goファイルをビルドします。

go build

実行ファイルが生成されます。

./hello

以下のように表示されれば、プロジェクトの作成は完了です。

Hello, World!

Go モジュール

Goモジュールを初期化します。

go mod init github.com/naughty-ghost/hello

hogeモジュールを追加する場合は、以下のようになります。

go install github.com/naughty-ghost/hoge

go.modの未使用の Go モジュールを削除する、かつコード上で使用されているが go.mod に追加されていない Go モジュールを追加します。

go mod tidy

Go テスト

テストファイルを作成します。

touch main_test.go

main_test.goファイルに以下のコードを記述します。

package main

import "testing"

func TestHello(t *testing.T) {
  got := Hello()
  want := "Hello, World!"

  if got != want {
    t.Errorf("got %q want %q", got, want)
  }
}

テストを実行します。

go test

以下のように表示されれば、テストは成功です。

ok      github.com/naughty-ghost/hello 0.001s

命名規則

Go言語の命名規則は以下の通りです。

参考にしたページ

Go + Docker でマルチステージビルドを使用する

マルチステージビルドを使用すると、ビルド時に必要なファイルをコピーするだけで、 実行時には不要なファイルを含めないことができる。 以下のように、マルチステージビルドを使用する。

FROM golang:1.22 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o app main.go

FROM alpine
COPY --from=build /app/app /app/
WORKDIR /app
CMD ["./app"]

Go + Docker でscratchイメージを使用するときの注意

cgoを無効化する

cgoが有効なままビルドすると、scratchイメージで実行できない。 ビルド時にcgoを無効化するためには、CGO_ENABLED=0を環境変数に設定する。

CGO_ENABLED=0 go build -o app main.go

ルートCA証明書をコピーする

ルートCA証明書をコピーしないと、Dockerイメージ内でSSL通信ができない。 ルートCA証明書を別のイメージからコピーすることができる。今回は、golang:1.22の/etc/ssl/certs/ca-certificates.crtを使用する。 Dockerfileに以下を追加する。

FROM golang:1.22 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o app main.go

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # 証明書のコピー
COPY --from=build /app/app /app/
WORKDIR /app
CMD ["./app"]

scratchイメージのタイムゾーンを設定する

scratchイメージにはタイムゾーンが設定されていない。 以下のように、タイムゾーンを設定する。

FROM scratch
COPY --from=golang:1.22 /usr/share/zoneinfo /usr/share/zoneinfo
ENV TZ=Asia/Tokyo

もしくはソースコードのmainパッケージでtime/tzdataをインポートする。

import _ "time/tzdata"

Dockerイメージのサイズを小さくする

scratchイメージを使用すると、Dockerイメージのサイズを小さくすることができる。 以下のように、scratchをベースイメージに指定する。

FROM scratch

Go + PostgreSQL

id に自動生成のUUIDを設定する

"github.com/gofrs/uuid" "gorm.io/driver/postgres" "gorm.io/gorm" パッケージを使用して、idに自動生成のUUIDを設定する。

Id uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`

マイグレーション時にuuid_generate_v4が存在しないエラーが発生したら次のSQLを実行する。

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

更新履歴