Skip to content

Go チートシート

シンプルさと並行性のために構築された、高速な静的型付け言語。

01

はじめに

Hello World

すべての Go プログラムは package main の main() 関数から開始されます。'go run' で実行、'go build' でコンパイルします。gofmt が自動的にコードをフォーマットします(タブ、スペース)。import 文でパッケージを取り込みます — fmt はフォーマット済み I/O を扱います。

go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
    fmt.Printf("Name: %s, Age: %d\n", "Alice", 30)
}

// Run: go run main.go
// Build: go build -o app main.go
// Format: gofmt -w main.go

Go モジュール(go.mod)

Go モジュール(go.mod)は Go 1.11 以降の依存関係管理です。'go mod init' でモジュールファイルを作成します。'go get' で依存関係を追加します。'go mod tidy' は未使用のインポートを削除し、不足分を追加します。モジュールパスがパッケージのインポートパスになります。

go
// Initialize a new module
// $ go mod init github.com/user/project

// go.mod file:
module github.com/user/project

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.3.0
)

// Add dependencies:
// $ go get github.com/gin-gonic/gin
// $ go mod tidy  // clean up unused deps

パッケージ構造

Go はパッケージでコードを整理します — ディレクトリごとに1パッケージ。大文字で始まる名前(Println、Add)はエクスポート(公開)、小文字はパッケージプライベートです。'internal' ディレクトリは親モジュールへのインポートを制限します。パッケージ名はディレクトリ名と一致させるべきです。

go
// project structure:
// myproject/
//   go.mod
//   main.go          // package main
//   utils/
//     helper.go      // package utils
//     math.go
//   internal/
//     api/           // private (only importable within parent)

// main.go
package main

import (
    "fmt"
    "github.com/user/project/utils"
)

func main() {
    fmt.Println(utils.Add(1, 2))
}

// Exported names start with uppercase letter
// unexported names start with lowercase letter

変数と定数

明示的宣言には 'var name type = value'、短縮宣言には 'name := value'(関数内のみ)を使用します。Go にはゼロ値があります:数値は 0、文字列は ''、ブール値は false、ポインタ/スライス/マップは nil。定数(const)はコンパイル時で := は使用できません。

go
package main

import "fmt"

// Package-level declarations
var version = "1.0.0" // inferred type
const Pi = 3.14159

func main() {
    // var with type
    var name string = "Alice"
    var age int = 30

    // Short declaration (only inside functions)
    city := "NYC"

    // Multiple assignment
    a, b, c := 1, 2, 3

    // Zero values (default)
    var x int      // 0
    var s string   // ""
    var ok bool    // false
    var p *int     // nil

    fmt.Println(name, age, city, a, b, c, x, s, ok, p)
}

基本型と変換

Go は明示的な型変換が必要です — C のような暗黙的変換はありません。rune は int32 のエイリアス(Unicode コードポイントを表す)。byte は uint8 のエイリアス。数値型間の変換は精度を失う可能性があります(float から int は切り捨て)。string(65) はコードポイントを文字に変換します。

go
// Numeric types
var i int = 42
var i8 int8 = 127
var u uint = 42
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793

// Other types
var b bool = true
var s string = "hello"
var r rune = 'A' // int32 alias for Unicode code point
var by byte = 255 // uint8 alias

// Type conversion (explicit, no implicit conversion)
var n int = 42
var f float64 = float64(n)  // int -> float64
var s string = string(65)   // 65 -> "A" (rune to string)
var i2 int = int(f)         // float64 -> int (truncates)

// Type inference
var x = 42        // int
var y = 3.14      // float64
var z = len("hi") // int
02

文字列とフォーマット

文字列の基礎

Go の文字列は不変のバイトシーケンスです。len() は文字数ではなくバイト数を返します — Unicode の場合は utf8.RuneCountInString() を使用してください。文字列は ==、<、> で辞書式に比較されます。文字列を range するとルーン(Unicode コードポイント)ごとに反復し、マルチバイト文字を正しく処理します。

go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World"

    // Length (bytes, not runes!)
    fmt.Println(len(s)) // 12

    // Concatenation
    s1 := "Hello" + " " + "World"
    s2 := fmt.Sprintf("%s = %d", "age", 30)

    // Comparison
    fmt.Println("abc" == "abc") // true
    fmt.Println("a" < "b")      // true (lexicographic)

    // Iteration (by rune)
    for i, r := range "Go语" {
        fmt.Printf("%d: %c\n", i, r)
    }
}

strings パッケージ

strings パッケージは一般的な文字列操作を提供します。Contains/HasPrefix/HasSuffix で部分文字列をチェックします。Split はデリミタで分割し、Join は結合します。Replace はカウントを取ります(-1 で全て)。TrimSpace は前後の空白を削除します。すべての関数は新しい文字列を返します(文字列は不変です)。

go
import (
    "fmt"
    "strings"
)

s := "Hello, World"

// Case
strings.ToUpper(s)         // "HELLO, WORLD"
strings.ToLower(s)         // "hello, world"
strings.Title("hello world") // "Hello World"

// Search
strings.Contains(s, "World")  // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "World") // true
strings.Index(s, "World")     // 7 (-1 if not found)
strings.Count(s, "l")         // 3

// Split & Join
parts := strings.Split("a,b,c", ",")  // ["a", "b", "c"]
joined := strings.Join(parts, "-")    // "a-b-c"

// Replace
strings.Replace(s, "l", "L", 1)  // "HeLlo, World" (1 occurrence)
strings.ReplaceAll(s, "l", "L")  // "HeLLo, WorLd"
strings.TrimSpace("  hi  ")      // "hi"

strconv とフォーマット

strconv は文字列と数値間の変換を行います — 常にエラー戻り値をチェックしてください。fmt.Printf が出力をフォーマットします:%d(int)、%f(float)、%s(string)、%t(bool)、%T(型)、%x(16進数)、%q(引用符付き)。小数点以下2桁には %.2f、ゼロ埋めには %05d を使用します。Sprintf は印刷せずに文字列を返します。

go
import (
    "fmt"
    "strconv"
)

// String to number
n, err := strconv.Atoi("42")     // string -> int
f, err := strconv.ParseFloat("3.14", 64)

// Number to string
s1 := strconv.Itoa(42)            // int -> string
s2 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"

// Printf formatting
fmt.Printf("Int: %d\n", 42)
fmt.Printf("Float: %.2f\n", 3.14159)
fmt.Printf("String: %s\n", "hi")
fmt.Printf("Bool: %t\n", true)
fmt.Printf("Type: %T\n", 42)     // int
fmt.Printf("Hex: %x\n", 255)     // ff
fmt.Printf("Pad: %05d\n", 42)    // 00042
fmt.Printf("Quote: %q\n", "hi")  // "hi"

// Sprintf returns formatted string
result := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)

Raw 文字列と複数行

Raw 文字列(バッククォート)はすべてを文字通りに保持します — エスケープシーケンスなし、複数行にまたがれます。正規表現、SQL、HTML テンプレートに使用します。解釈文字列(ダブルクォート)は \n、\t などを処理します。ループ内の効率的な文字列連結には strings.Builder を使用してください(O(n²) の割り当てを回避)。

go
// Raw string literal (backticks) - no escape processing
raw := `This is a
multiline string
with \n (literal backslash-n)`

// Interpreted string (double quotes) - processes escapes
interpreted := "Line1\nLine2\tTabbed"

// Raw strings are useful for:
// - Regex patterns
// - HTML/SQL templates
// - File paths on Windows
regex := `^d{4}-d{2}-d{2}$`

// String builder (efficient concatenation)
var sb strings.Builder
for i := 0; i < 1000; i++ {
    sb.WriteString("line\n")
}
result := sb.String()

Unicode とルーン

Go の文字列は UTF-8 エンコードされたバイトシーケンスです。len() はバイト数、utf8.RuneCountInString() は文字数を返します。文字列の range は UTF-8 を自動的にデコードします。文字列を変更するには、[]rune に変換して変更し、戻します。国際化テキスト処理に不可欠です。

go
import (
    "fmt"
    "unicode/utf8"
)

s := "Hello, 世界"

// Byte length vs rune length
fmt.Println(len(s))                    // 13 (bytes)
fmt.Println(utf8.RuneCountInString(s)) // 9 (runes/characters)

// Decode runes manually
for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%c ", r)
    i += size
}

// Range automatically decodes runes
for i, r := range s {
    fmt.Printf("%d:%c ", i, r)
}

// Rune slice (mutable)
runes := []rune("Hello")
runes[0] = 'J'
s2 := string(runes) // "Jello"
03

制御フロー

If / Else

Go の if は条件に括弧不要ですが、ブレースは必須です(単文本体でも)。初期化文(if x := f(); x > 0)は if/else ブロックにスコープされます — エラーチェックに一般的です。このパターンは変数スコープを狭く保ちます。

go
// Basic if/else
score := 85
if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else {
    fmt.Println("C")
}

// If with initialization statement
if n := computeValue(); n > 100 {
    fmt.Println("big:", n)
} else {
    fmt.Println("small:", n)
}
// n is not accessible here (scoped to if)

// No parentheses needed, but braces are required

For ループ

Go にはループキーワードが1つだけです:'for'。C スタイル、while スタイル(for cond)、無限(for)、反復(for range)を処理します。range はスライス、マップ、文字列、チャネルで動作します。インデックスや値をスキップするには _ を使用します。マップの反復順序は設計上ランダムです。

go
// C-style for
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While-style (condition only)
n := 10
for n > 0 {
    n--
}

// Infinite loop
for {
    break
}

// Range (iterate slices, maps, strings)
nums := []int{10, 20, 30}
for index, value := range nums {
    fmt.Printf("%d: %d\n", index, value)
}

// Skip index or value with _
for _, value := range nums {
    fmt.Println(value)
}

// Range over map (unordered!)
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
    fmt.Println(key, val)
}

Switch

Go の switch はデフォルトでフォールスルーしません(C/Java とは異なります) — 各 case は独立したブランチです。強制するには fallthrough を使用します。case ごとにカンマで複数の値を指定できます。式なしの switch は if/else チェーンよりクリーンです。初期化文付きの switch は変数を switch にスコープします。

go
// Basic switch
day := 3
switch day {
case 1:
    fmt.Println("Mon")
case 2, 3, 4, 5:
    fmt.Println("Weekday")
case 6, 7:
    fmt.Println("Weekend")
default:
    fmt.Println("Invalid")
}

// Switch with no expression (like if/else chain)
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
default:
    grade = "C"
}

// Switch with init
switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux")
case "darwin":
    fmt.Println("macOS")
}

// Fallthrough (rare, goes to next case unconditionally)
switch 1 {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two") // executes even though value is 1
}

Defer

defer は囲む関数が戻る時に実行する関数呼び出しをスケジュールします — LIFO(後入れ先出し)。クリーンアップ(ファイルクローズ、ロック解放、接続クローズ)に使用します。遅延呼び出しの引数は即座に評価されますが、呼び出しは戻り時に実行されます。関数がパニックしても defer は実行されます。

go
// Defer runs when function returns (LIFO order)
func main() {
    defer fmt.Println("third")
    defer fmt.Println("second")
    fmt.Println("first")
}
// Output: first, second, third

// Common: resource cleanup
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // runs when readFile returns

    // ... use f ...
    return nil
}

// Defer evaluates arguments immediately
i := 1
defer fmt.Println(i) // prints 1 (not 2)
i = 2

Goto、Break、Continue

break は最内ループを終了し、continue は次の反復にスキップします。ラベル(break outer)でネストされたループを終了します。goto は存在しますが稀にしか使用されません — 構造化制御フローを優先してください。ラベルは continue と組み合わせて外側のループの次の反復にスキップすることもできます。

go
// Break and continue
for i := 0; i < 10; i++ {
    if i%2 == 0 {
        continue // skip even numbers
    }
    if i > 7 {
        break // exit loop
    }
    fmt.Println(i)
}

// Break with label (for nested loops)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer // exits both loops
        }
    }
}

// Goto (rarely used, avoid)
i := 0
loop:
    if i < 5 {
        fmt.Println(i)
        i++
        goto loop
    }

Select(チャネル操作)

select はチャネル用の switch のようなものです — 複数のチャネル操作を待機し、準備できた最初のものを選びます。default ケースで非ブロックにします。イベント駆動パターンのループで select を使用します。time.After() はタイムアウトチャネルを作成します。複数のケースが準備済みの場合、select はランダムに選び、スターベーションを防ぎます。

go
// Select waits on multiple channel operations
select {
case msg := <-messages:
    fmt.Println("received:", msg)
case <-timeout:
    fmt.Println("timed out")
default:
    fmt.Println("no activity") // non-blocking
}

// Select with send
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()

select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
}

// Select in a loop (event loop pattern)
for {
    select {
    case msg := <-ch:
        fmt.Println(msg)
    case <-time.After(5 * time.Second):
        return // timeout after 5s of inactivity
    }
}
04

関数

定義と複数戻り値

Go 関数は複数の値を返せます — 標準的なパターンは (result, error) です。名前付き戻り値は宣言された変数として扱われ、ゼロ値で初期化されます。「裸の」return はそれらを使用します。名前付き戻り値は複雑な関数の可読性を向上させますが、乱用すると混乱を招きます。常にエラーを即座にチェックしてください。

go
// Function with multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide by zero")
    }
    return a / b, nil
}

// Named return values
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // "naked" return (uses named values)
}

// Usage
result, err := divide(10, 2)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result) // 5

x, y := split(100)
fmt.Println(x, y)

可変長引数とクロージャ

可変長引数関数(...T)は任意の数の引数を受け入れ、スライスとして受け取ります。... でスライスを展開します。クロージャは囲むスコープから参照で変数をキャプチャします — counter() 関数は 'count' を記憶するクロージャを返します。クロージャはコールバック、イテレータ、状態保持関数に有用です。

go
// Variadic function (variable arguments)
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
fmt.Println(sum(1, 2, 3))       // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15

// Spread a slice
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // 6

// Closure (function that captures variables)
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3

関数を値として

Go の関数は第一級の値です — 変数に代入、引数として渡す、データ構造に格納できます。'type Name func(params) returns' で関数型を定義します。関数のマップはディスパッチテーブル、コマンドハンドラ、ストラテジパターンに有用です。

go
// Functions are first-class values
func apply(f func(int) int, x int) int {
    return f(x)
}

func double(x int) int { return x * 2 }
func square(x int) int { return x * x }

fmt.Println(apply(double, 5)) // 10
fmt.Println(apply(square, 5)) // 25

// Function type
type MathFunc func(int) int

var fn MathFunc = func(x int) int { return x + 1 }
fmt.Println(fn(10)) // 11

// Map of functions
ops := map[string]func(int, int) int{
    "add": func(a, b int) int { return a + b },
    "sub": func(a, b int) int { return a - b },
    "mul": func(a, b int) int { return a * b },
}
fmt.Println(ops["add"](3, 4)) // 7

init() と匿名関数

init() 関数は main() の前に自動的に実行されます。セットアップ(設定読み込み、バリデーション、登録)に使用します。ファイルごとに複数の init() が許可されます。匿名関数は即時実行(IIFE)や変数への代入が可能です。ゴルーチンにも使用されます。

go
// init() runs before main(), once per package
// Multiple init() functions allowed, run in order declared
package main

import "fmt"

var config string

func init() {
    // Setup code: config, connections, validation
    config = "production"
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2, config:", config)
}

func main() {
    fmt.Println("main")
}
// Output: init 1, init 2, main

// Anonymous function (IIFE)
result := func(x int) int {
    return x * 2
}(5) // immediately invoked, result = 10

// Goroutine with anonymous function
go func(msg string) {
    fmt.Println(msg)
}("async")

ジェネリクス(Go 1.18+)

Go 1.18+ は型パラメータ [T any] でジェネリクスをサポートします。型制約(インターフェース)が許可される型を制限します — 制約なしには 'any'、== / != 演算子には 'comparable' を使用します。ユニオン制約(int | float64)で特定の型を許可します。ジェネリクスにより、コード重複なしの型安全な再利用可能データ構造とアルゴリズムが可能です。

go
// Generic function with type parameter
func Map[T, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

nums := []int{1, 2, 3}
doubled := Map(nums, func(n int) int { return n * 2 })
// [2, 4, 6]

strs := Map(nums, func(n int) string {
    return fmt.Sprintf("num%d", n)
})
// ["num1", "num2", "num3"]

// Type constraints
type Number interface {
    int | int64 | float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// comparable constraint (for maps/slices keys)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

メソッド

メソッドはレシーバ引数を持つ関数です。値レシーバ(r Rectangle)はコピーで動作し — 元を変更できません。ポインタレシーバ(r *Rectangle)は変更でき、大きな構造体のコピーを回避します。一貫性が重要です:1つのメソッドがポインタレシーバを使用する場合、すべてそうすべきです。同じパッケージの任意の型にメソッドを定義できます。

go
type Rectangle struct {
    Width, Height float64
}

// Method with value receiver
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Method with pointer receiver (can modify)
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// Method on non-struct types
type MyString string

func (s MyString) Shout() string {
    return strings.ToUpper(string(s)) + "!"
}

// Usage
r := Rectangle{Width: 10, Height: 5}
fmt.Println(r.Area()) // 50
r.Scale(2)
fmt.Println(r.Width)  // 20

s := MyString("hello")
fmt.Println(s.Shout()) // HELLO!
05

データ構造

配列とスライス

配列は固定長、スライスは動的(配列がベース)。append() は要素を追加し、必要に応じて容量を増やします。make([]T, len, cap) で事前割り当てして効率化。スライスはビューを作成(基盤配列を共有) — 独立したデータには copy() を使用してください。最適化時に len/cap を常にチェックしてください。

go
// Array (fixed length, rarely used directly)
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // size inferred

// Slice (dynamic array, most common)
nums := []int{1, 2, 3}
nums = append(nums, 4)        // [1, 2, 3, 4]
nums = append(nums, 5, 6, 7) // [1, 2, 3, 4, 5, 6, 7]

// Make a slice with capacity
s := make([]int, 3, 10) // len=3, cap=10

// Slicing
sub := nums[1:4]  // [2, 3, 4]
first := nums[:2] // [1, 2]
last := nums[3:]  // [4, 5, 6, 7]

// Copy
dst := make([]int, len(nums))
copy(dst, nums)

// Length and capacity
fmt.Println(len(nums), cap(nums))

マップ

マップは Go のハッシュテーブルです — 順序なしのキーと値のペア。カンマok パターン(val, ok := m[key])でキーの存在をチェックします。delete() でキーを削除します。マップの反復順序は設計上ランダムです。マップは参照型 — 関数に渡すと基盤データを共有します。nil マップには書き込めません(make() を使用してください)。

go
// Create a map
m := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

// Using make
ages := make(map[string]int)
ages["Charlie"] = 35

// Access
fmt.Println(m["Alice"]) // 30

// Check existence (comma-ok pattern)
age, ok := m["David"]
if !ok {
    fmt.Println("not found")
}

// Delete
delete(m, "Bob")

// Iterate (random order)
for name, age := range m {
    fmt.Printf("%s: %d\n", name, age)
}

// Length
fmt.Println(len(m))

// Nested maps
matrix := map[string]map[string]int{}
matrix["row1"] = map[string]int{"col1": 1}

構造体

構造体は関連フィールドをグループ化します。明確さのために名前付き初期化(Person{Name: ...})を使用してください。new() はゼロ値のポインタを返します。匿名構造体は使い捨てのデータ形状に有用です。構造体の埋め込み(フィールド名なし)は埋め込まれた構造体のフィールドとメソッドを昇格します — Go の継承の代替。

go
// Define a struct
type Person struct {
    Name string
    Age  int
    Address string // field names are exported (capitalized)
}

// Create instances
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25, "NYC"} // positional (not recommended)
p3 := new(Person)              // returns *Person, zero values
p3.Name = "Charlie"

// Anonymous struct (one-off)
config := struct {
    Port int
    Host string
}{
    Port: 8080,
    Host: "localhost",
}

// Struct embedding (composition)
type Employee struct {
    Person          // embedded (promotes fields)
    Salary   float64
}

emp := Employee{
    Person: Person{Name: "Dave", Age: 40},
    Salary: 50000,
}
fmt.Println(emp.Name) // "Dave" (promoted from Person)

ポインタ

Go ポインタ(*T)はメモリアドレスを保持します。& でアドレスを取得、* でデリファレンスします。C とは異なり、Go にはポインタ演算がありません(より安全)。構造体ポインタは省略記法を許可((*u).Name の代わりに u.Name)。nil ポインタのデリファレンスはパニックします。Go にはガベージコレクションがあります — 手動の free() は不要です。

go
// Pointer basics
x := 42
p := &x          // p is *int, points to x
fmt.Println(*p)  // 42 (dereference)
*p = 100         // modify x through pointer
fmt.Println(x)   // 100

// new() allocates and returns pointer
p2 := new(int)   // *int, value 0
*p2 = 42

// Pointers to structs
type User struct{ Name string }
u := &User{Name: "Alice"} // *User
u.Name = "Bob"            // (*u).Name shorthand
fmt.Println(u.Name)       // Bob

// nil pointer
var p3 *int // nil
// *p3 // panic: nil pointer dereference

// No pointer arithmetic (unlike C)
// p++ is illegal

スライス操作

Go には組み込みの filter/map/reduce がありません — 自分で書くか slices パッケージ(Go 1.21+)を使用してください。Insert/remove は append+copy で要素をシフトする必要があります。スライスのエイリアシングに注意:s[:i] と s[i+1:] は基盤配列を共有します。2D スライスには各行を個別に割り当ててください。

go
// Filter
func filter(nums []int, pred func(int) bool) []int {
    result := []int{}
    for _, n := range nums {
        if pred(n) {
            result = append(result, n)
        }
    }
    return result
}

evens := filter([]int{1,2,3,4,5}, func(n int) bool {
    return n%2 == 0
}) // [2, 4]

// Insert at index
func insert(s []int, i, v int) []int {
    s = append(s, 0)
    copy(s[i+1:], s[i:])
    s[i] = v
    return s
}

// Remove at index
func remove(s []int, i int) []int {
    return append(s[:i], s[i+1:]...)
}

// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
    matrix[i] = make([]int, 4)
}

ソートと検索

sort.Ints/Strings/Float64s はインプレースでソートします。sort.Slice と比較関数でカスタム型を処理します。sort.Search* はソート済みスライスで二分検索を行います。Go 1.21+ はジェネリックソート関数を持つ slices パッケージを追加します。すべてのソートはインプレースです — 元の順序が必要な場合はコピーを作成してください。

go
import "sort"

// Sort a slice
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(nums) // [1 1 2 3 4 5 6 9]
fmt.Println(nums)

// Sort strings
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs) // [apple banana cherry]

// Sort with custom comparator
type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}

sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age // by age ascending
})

// Binary search (sorted slice)
idx := sort.SearchInts(nums, 4) // index of 4

// Sort package (Go 1.21+): slices.Sort
import "slices"
slices.Sort(people) // needs Less method or cmp func
06

メソッドとインターフェース

インターフェースの定義

インターフェースはメソッドシグネチャを定義します。Go は構造的型付けを使用 — 型は必要なメソッドをすべて持っていれば自動的にインターフェースを実装します(明示的な 'implements' 宣言なし)。これにより疎結合設計が可能になります:実装する場所ではなく使用する場所でインターフェースを定義してください。

go
// Interface: a set of method signatures
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Implement implicitly (no 'implements' keyword)
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Use the interface
func describe(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
        s.Area(), s.Perimeter())
}

c := Circle{Radius: 5}
describe(c) // works! Circle implements Shape

空のインターフェースと型アサーション

interface{}(または Go 1.18+ では 'any')は任意の値を保持します。型アサーション(v.(T))で具象型を抽出します — 間違った型だとパニックするため、カンマok パターンを使用してください。型 switch(switch v.(type))で複数の型をクリーンに処理します。空のインターフェースは汎用コンテナに有用ですが型安全性を失います — ジェネリクスを優先してください。

go
// Empty interface (any type)
// Go 1.18+: 'any' is an alias for interface{}
func printAny(v any) {
    fmt.Println(v)
}

printAny(42)
printAny("hello")
printAny([]int{1, 2, 3})

// Type assertion
var i any = "hello"
s := i.(string)    // panics if not string
fmt.Println(s)

// Safe type assertion (comma-ok)
s, ok := i.(string)
if ok {
    fmt.Println("string:", s)
}

// Type switch
func describe(v any) {
    switch x := v.(type) {
    case int:
        fmt.Printf("int: %d\n", x)
    case string:
        fmt.Printf("string: %s\n", x)
    case []int:
        fmt.Printf("int slice: %v\n", x)
    default:
        fmt.Printf("unknown type: %T\n", x)
    }
}

インターフェースの合成

インターフェースは他のインターフェースを埋め込めます(合成)。io.Reader と io.Writer は Go の最も重要なインターフェースです — ファイル、ネットワーク接続、バッファなどで実装されています。これにより強力な抽象化が可能になります:io.Reader を取る関数は任意の読み取り可能なソースで動作します。小さく焦点を絞ったインターフェース(1-3メソッド)が慣用的です。

go
// Compose interfaces
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter combines Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}

// io.Reader and io.Writer are built-in interfaces
// Many types implement them: *os.File, *bytes.Buffer, net.Conn

func copyData(r io.Reader, w io.Writer) error {
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        if n > 0 {
            if _, err := w.Write(buf[:n]); err != nil {
                return err
            }
        }
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
    }
    return nil
}

ポインタ vs 値レシーバ

ポインタレシーバは構造体を変更でき、大きな値のコピーを回避します。値レシーバは安全(変更不可)で、値とポインタの両方でメソッドを使用できます。いずれかのメソッドがポインタレシーバを持つ場合、すべてそうすべきです(一貫性のため)。ポインタレシーバメソッドは T と *T の両方でインターフェースを満たしますが、値レシーバメソッドは T のみです。

go
type Counter struct {
    count int
}

// Value receiver: works on a copy, can't modify
func (c Counter) Get() int {
    return c.count
}

// Pointer receiver: can modify the original
func (c *Counter) Increment() {
    c.count++
}

c := Counter{}
c.Increment() // Go auto-takes address (&c).Increment()
c.Increment()
fmt.Println(c.Get()) // 2

// Interface implementation note:
// *Counter implements both Get() and Increment()
// Counter (value) implements only Get()
// So *Counter satisfies an interface requiring both

Stringer インターフェース

Stringer インターフェース(String() string)は型が Print/Printf でどのように表示されるかを制御します — Java/JS の toString() のようなもの。%v は String() を使用し、%+v はフィールド名を表示し、%#v は Go 構文を表示します。error インターフェースも同様に動作します:Error() string を実装すると任意の型がエラーになります。これらは Go の最も一般的な組み込みインターフェースです。

go
// Stringer interface (like toString() in other languages)
type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

// Implement Stringer
func (p Person) String() string {
    return fmt.Sprintf("%s (%d years)", p.Name, p.Age)
}

p := Person{Name: "Alice", Age: 30}
fmt.Println(p)       // Alice (30 years) — uses String()
fmt.Printf("%v\n", p) // Alice (30 years)
fmt.Printf("%+v\n", p) // Name:Alice Age:30
fmt.Printf("%#v\n", p) // main.Person{Name:"Alice", Age:30}

// Error interface is similar:
// type error interface { Error() string }
// Implement Error() to make a type usable as an error

型の埋め込みと合成

Go は継承の代わりに合成を使用します。構造体を埋め込むと、そのフィールドとメソッドが外側の構造体に昇格します。外側の型で同名のメソッドを定義して昇格されたメソッドをオーバーライドできます。インターフェースを埋め込むとデコレート/委譲が可能です — 外側の型がインターフェースを満たし、呼び出しを転送できます。

go
// Embed a struct (composition over inheritance)
type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

type Dog struct {
    Animal // embedded — promotes Name and Speak()
    Breed  string
}

d := Dog{
    Animal: Animal{Name: "Rex"},
    Breed:  "Labrador",
}

fmt.Println(d.Name)      // "Rex" (promoted)
fmt.Println(d.Speak())   // "Rex makes a sound" (promoted)
fmt.Println(d.Animal.Name) // explicit access also works

// Override a promoted method
func (d Dog) Speak() string {
    return d.Name + " barks!"
}
fmt.Println(d.Speak()) // "Rex barks!"

// Embed an interface
type Logger struct {
    io.Writer // embed interface
}
// Logger now has Write() method, delegating to the embedded Writer
07

エラー処理

エラーの基礎

Go は例外ではなく値としてエラーを処理します。関数は (result, error) を返します — 常に err != nil を即座にチェックしてください。errors.New() はシンプルなエラーを作成し、fmt.Errorf() はフォーマットを追加します。センチネルエラー(var ErrX = errors.New())で == による比較が可能です。エラーを無視しないでください(意図的な場合のみ _ を使用)。

go
// Functions return errors as last return value
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

// Always check errors immediately
result, err := divide(10, 0)
if err != nil {
    log.Fatal(err) // or handle gracefully
}
fmt.Println(result)

// fmt.Errorf for formatted errors
func validate(age int) error {
    if age < 0 {
        return fmt.Errorf("invalid age: %d (must be positive)", age)
    }
    return nil
}

// Sentinel errors
var ErrNotFound = errors.New("not found")
if err := lookup("key"); err == ErrNotFound {
    // handle not found
}

カスタムエラー型

カスタムエラー型は error インターフェース(Error() string)を実装します。シンプルなメッセージを超えた構造化データ(フィールド、コード、コンテキスト)を運びます。型アサーション(*ValidationError)でデータにアクセスします。ドメイン固有のエラー処理に不可欠です — 例:HTTP ステータスコード、バリデーション詳細、リトライロジック。

go
// Custom error type (implements error interface)
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on '%s': %s", e.Field, e.Message)
}

// Use the custom error
func validateUser(name string) error {
    if name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "cannot be empty",
        }
    }
    return nil
}

// Type-assert to access fields
err := validateUser("")
if ve, ok := err.(*ValidationError); ok {
    fmt.Println("Field:", ve.Field)   // "name"
    fmt.Println("Message:", ve.Message) // "cannot be empty"
}

errors.Is と errors.As(Go 1.13+)

Go 1.13+ は %w(fmt.Errorf)によるエラーラッピングを追加しました。errors.Is() はエラーがセンチネルにマッチするかチェックします(チェーンをアンラップ)。errors.As() はチェーンから特定のエラー型を抽出します。これにより階層化されたエラー処理が可能です:低レベルエラーにコンテキストを付けてラップし、高レベルでチェックします。チェーンを保持するには常に %w(%v ではなく)でラップしてください。

go
import "errors"

// Wrapped errors with %w
var ErrNotFound = errors.New("not found")

func getUser(id int) error {
    if id == 0 {
        return fmt.Errorf("getUser(%d): %w", id, ErrNotFound)
    }
    return nil
}

// errors.Is: check if error matches (unwraps the chain)
err := getUser(0)
if errors.Is(err, ErrNotFound) {
    fmt.Println("user not found")
}

// errors.As: extract a specific error type
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("field:", ve.Field)
}

// errors.Unwrap: get the wrapped error
inner := errors.Unwrap(err)

Panic と Recover

panic() は回復不能なエラー(バグ、不変条件違反)用です — 通常のエラー処理ではありません。recover() はパニックをキャッチしますが、遅延関数内でのみ動作します。panic/recover の使用:プログラミングエラー(インデックス範囲外)、パッケージ初期化失敗、ゴルーチンのクラッシュからの保護。予期される失敗にはエラーを返すことを優先してください。

go
// Panic: unrecoverable error (like throw)
func mustParse(s string) int {
    n, err := strconv.Atoi(s)
    if err != nil {
        panic(fmt.Sprintf("invalid number: %s", s))
    }
    return n
}

// Recover: catch a panic (only in deferred functions)
func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("something went wrong")
}

// Practical: safe goroutine
func safeGo(fn func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Println("goroutine panic:", r)
            }
        }()
        fn()
    }()
}

エラーラッピングパターン

各レイヤーで %w を使用した fmt.Errorf でコンテキスト付きのエラーをラップします。これによりエラーチェーンが作成されます:トップレベルは完全なパスを見ます(getUserProfile → fetchUser → sql error)。コンテキスト(関数名、パラメータ)がデバッグに役立ちます。errors.Is/As でチェーン内の特定のエラーをチェックします。同じコンテキストで同じエラーを複数回ラップするのは避けてください。

go
// Wrap errors with context as they propagate up
func fetchUser(id int) (*User, error) {
    row := db.QueryRow("SELECT ... WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.Name, &u.Age); err != nil {
        return nil, fmt.Errorf("fetchUser(%d): %w", id, err)
    }
    return &u, nil
}

func getUserProfile(id int) (*Profile, error) {
    user, err := fetchUser(id)
    if err != nil {
        return nil, fmt.Errorf("getUserProfile(%d): %w", id, err)
    }
    // ...
}

// At the top level, log the full chain
profile, err := getUserProfile(42)
if err != nil {
    log.Printf("error: %v", err)
    // Output: getUserProfile(42): fetchUser(42): sql: no rows
}

// golang.org/x/xerrors or pkg/errors for stack traces

Defer、Panic、Recover の組み合わせ

defer(クリーンアップ)、panic(致命的エラー)、recover(パニックのキャッチ)を組み合わせて堅牢なリソース管理を実現します。遅延呼び出しはパニック中でも LIFO 順序で実行されます。名前付き戻り値(err error)は遅延 recover 内で設定できます。このパターンにより、コードがパニックしてもファイル/接続がクローズされ、トランザクションがロールバックされます。

go
// Complete pattern: cleanup + panic recovery
func processFile(path string) (err error) {
    file, e := os.Open(path)
    if e != nil {
        return e
    }
    defer func() {
        // Close file regardless of panic
        file.Close()

        // Recover from panic and convert to error
        if r := recover(); r != nil {
            err = fmt.Errorf("panic in processFile: %v", r)
        }
    }()

    // Do work that might panic
    data := parseFile(file) // might panic
    return saveData(data)
}

// Defer for resource cleanup (always runs)
func withDatabase(db *sql.DB, fn func(*sql.DB) error) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        if err != nil {
            tx.Rollback()
        } else {
            tx.Commit()
        }
    }()
    return fn(tx)
}
08

並行処理

ゴルーチン

ゴルーチンは Go の軽量スレッドです — 'go' で開始します。安価(約2KB スタック)で Go のランタイムスケジューラ(M:N スケジューリング)で管理されます。main 関数はゴルーチンを待ちません — 同期には sync.WaitGroup かチャネルを使用してください。本番で同期に time.Sleep を使用しないでください(WaitGroup を使用)。

go
package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Println(name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Launch goroutine with 'go' keyword
    go sayHello("Alice")
    go sayHello("Bob")

    // Anonymous goroutine
    go func() {
        fmt.Println("anonymous goroutine")
    }()

    // Wait for goroutines (simple approach)
    time.Sleep(1 * time.Second)
    fmt.Println("done")
}

// Goroutines are lightweight (~2KB stack, grows as needed)
// Millions of goroutines can run concurrently

チャネル

チャネルはゴルーチン通信用の型付き導管です。バッファなし(make(chan T))は送信側と受信側の両方が準備できるまでブロックします(同期)。バッファ付き(make(chan T, n))は満杯の時のみブロックします(非同期)。チャネルをクローズするのは送信側で、受信側ではありません。チャネルがクローズされるまで range します。チャネルにより「通信によるメモリ共有」が可能です。

go
// Unbuffered channel (synchronous)
ch := make(chan string)

// Send and receive
go func() {
    ch <- "hello" // blocks until received
}()
msg := <-ch // blocks until sent
fmt.Println(msg)

// Buffered channel (asynchronous)
buf := make(chan int, 3)
buf <- 1 // doesn't block (buffer has space)
buf <- 2
buf <- 3
// buf <- 4 // would block (buffer full)
fmt.Println(<-buf) // 1

// Close a channel (sender closes, never receiver)
close(buf)

// Range over channel (until closed)
for v := range buf {
    fmt.Println(v)
}

// Check if closed
v, ok := <-buf
if !ok {
    fmt.Println("channel closed")
}

Select 文

select はゴルーチンが複数のチャネル操作を待機できるようにします — 準備できた最初のものを選びます(複数の場合はランダム)。default ケースで非ブロックにします。time.After() はタイムアウトチャネルを作成します。select は並行 Go の心臓部です:イベントループ、fan-in/fan-out、タイムアウト。デッドロックを避けるため常にタイムアウトを含めてください。

go
// Select: multiplex channel operations
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(1 * time.Second)
    ch1 <- "one"
}()
go func() {
    time.Sleep(2 * time.Second)
    ch2 <- "two"
}()

// Wait for first to arrive
for i := 0; i < 2; i++ {
    select {
    case msg1 := <-ch1:
        fmt.Println("received:", msg1)
    case msg2 := <-ch2:
        fmt.Println("received:", msg2)
    }
}

// Timeout with select
select {
case result := <-slowOperation():
    fmt.Println(result)
case <-time.After(3 * time.Second):
    fmt.Println("timeout!")
}

// Non-blocking receive (default case)
select {
case msg := <-ch:
    fmt.Println(msg)
default:
    fmt.Println("no message") // runs if no data
}

sync.WaitGroup

sync.WaitGroup はゴルーチンのグループの終了を待機します。Add(n) がカウンタを増やし、Done() が減らし(defer で使用)、Wait() がゼロになるまでブロックします。クロージャキャプチャのバグを避けるため、ループ変数はゴルーチンにパラメータとして渡してください(Go 1.22 で修正されましたが推奨)。WaitGroup はファイアアンドフォーゲット並行処理にはチャネルよりシンプルです。

go
import "sync"

func main() {
    var wg sync.WaitGroup

    // Launch 5 goroutines
    for i := 0; i < 5; i++ {
        wg.Add(1) // increment counter
        go func(id int) {
            defer wg.Done() // decrement when done
            fmt.Printf("Worker %d started\n", id)
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // block until all Done() called
    fmt.Println("all workers finished")
}

// Common mistake: passing loop variable to goroutine
// Always pass as parameter: go func(i int) { ... }(i)
// (Go 1.22+ fixes loop variable scoping, but still good practice)

Mutex と Sync

sync.Mutex は並行アクセスから共有状態を保護します — defer で Lock/Unlock。RWMutex は複数の読み取り側または1つの書き込み側を許可します(読み取り多いワークロードに適しています)。sync.Once は初期化が正確に1回実行されることを保証します(シングルトンパターン)。通信にはチャネルを、共有状態の保護にはミューテックスを優先してください。「通信によるメモリ共有、メモリ共有による通信ではない。」

go
import "sync"

// Mutex: mutual exclusion lock
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: multiple readers, one writer
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple allowed)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *Cache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

// sync.Once: run exactly once
var (
    once sync.Once
    instance *Database
)

func GetDB() *Database {
    once.Do(func() {
        instance = &Database{}
    })
    return instance
}

並行パターン

ワーカープール:固定数のゴルーチンがチャネルからのジョブを処理し、結果を別のチャネルに送信します。これにより並行度が制限され、リソース枯渇を防ぎます。Fan-out/fan-in:作業をゴルーチンに分散し、結果をマージします。重要な洞察:データがこれ以上送信されない時にチャネルをクローズし、range ループが終了するようにします。これらのパターンが並行 Go の基盤です。

go
// Worker pool
func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        time.Sleep(time.Second) // simulate work
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Collect results
    for a := 1; a <= 5; a++ {
        fmt.Println(<-results)
    }
}

// Fan-out/Fan-in
func fanOut(input <-chan int, workers int) <-chan int {
    output := make(chan int)
    var wg sync.WaitGroup
    wg.Add(workers)
    for i := 0; i < workers; i++ {
        go func() {
            defer wg.Done()
            for v := range input {
                output <- v * 2
            }
        }()
    }
    go func() { wg.Wait(); close(output) }()
    return output
}
09

ファイル I/O と OS

ファイルの読み込み

os.ReadFile() はファイル全体をメモリに読み込みます(シンプルですが大きなファイルには不適切)。大きなファイルには os.Open() で開き、チャンクで読み込み、常に defer Close() を使用してください。bufio.Scanner は行ごとに読み込みます — テキスト処理に理想的です。常にエラーをチェックしてください。特に読み込み完了を知るための io.EOF。

go
import (
    "io"
    "os"
)

// Read entire file (small files)
data, err := os.ReadFile("input.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

// Open and read (large files)
file, err := os.Open("large.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Read in chunks
buf := make([]byte, 1024)
for {
    n, err := file.Read(buf)
    if n > 0 {
        // process buf[:n]
    }
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
}

// Read line by line
scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println(line)
}

ファイルの書き込み

os.WriteFile() は作成/切り詰めして原子的に書き込みます(シンプル)。os.OpenFile() とフラグで制御:O_APPEND(末尾に追加)、O_CREATE(存在しない場合作成)、O_TRUNC(切り詰め)。多数の小さな書き込みには bufio.Writer を使用(メモリでバッファ、最後にフラッシュ)。ファイルモード 0644:所有者は読み書き、他は読み取り可能。

go
// Write entire file (creates or truncates)
err := os.WriteFile("output.txt", []byte("Hello, World!"), 0644)
if err != nil {
    log.Fatal(err)
}

// Open for writing (with options)
file, err := os.OpenFile("log.txt",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Write
if _, err := file.WriteString("log entry\n"); err != nil {
    log.Fatal(err)
}

// Buffered writer (efficient for many small writes)
writer := bufio.NewWriter(file)
for i := 0; i < 1000; i++ {
    writer.WriteString(fmt.Sprintf("line %d\n", i))
}
writer.Flush() // don't forget to flush!

// File modes: 0644 = owner read/write, others read
// os.O_APPEND, O_CREATE, O_WRONLY, O_RDONLY, O_RDWR, O_TRUNC

ファイル操作

os.Mkdir/MkdirAll でディレクトリを作成。os.ReadDir でエントリを効率的にリスト。filepath.WalkDir でディレクトリを再帰的に巡回。os.Stat でファイル情報(サイズ、更新時刻、権限)。os.IsNotExist でファイル欠落をチェック。os.Remove で1ファイル削除、RemoveAll でディレクトリを再帰的に削除。常にエラーをチェックしてください。

go
// Create directory
os.Mkdir("mydir", 0755)       // one level
os.MkdirAll("a/b/c", 0755)    // nested directories

// List directory
entries, err := os.ReadDir(".")
if err != nil {
    log.Fatal(err)
}
for _, entry := range entries {
    fmt.Println(entry.Name(), entry.IsDir())
}

// Walk directory tree
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path)
    return nil
})

// File info
info, _ := os.Stat("file.txt")
fmt.Println(info.Size())      // bytes
fmt.Println(info.ModTime())   // last modified
fmt.Println(info.IsDir())

// Check existence
if _, err := os.Stat("file.txt"); os.IsNotExist(err) {
    fmt.Println("file does not exist")
}

// Remove
os.Remove("file.txt")
os.RemoveAll("directory") // recursive

環境変数とコマンドライン

os.Getenv/LookupEnv/Setenv で環境変数を管理。os.Args で生のコマンドライン引数。flag パッケージはデフォルトとヘルプテキスト付きの解析済みフラグを提供します。LookupEnv は未設定と空の値を区別します。環境変数は 12ファクターアプリを設定する標準的な方法です(API キー、データベース URL)。

go
// Environment variables
os.Setenv("API_KEY", "secret")
key := os.Getenv("API_KEY") // "" if not set

// With existence check
if val, ok := os.LookupEnv("HOME"); ok {
    fmt.Println("HOME:", val)
}

// All environment variables
for _, e := range os.Environ() {
    fmt.Println(e)
}

// Command line arguments
// args := os.Args // []string, args[0] is program name

// Using flag package
var port int
var host string
flag.IntVar(&port, "port", 8080, "server port")
flag.StringVar(&host, "host", "localhost", "server host")
flag.Parse()
fmt.Printf("Running on %s:%d\n", host, port)

// Run: go run main.go -port 3000 -host 0.0.0.0

コマンドの実行

os/exec は外部コマンドを実行します。Command() でコマンドを作成、Output() で stdout をキャプチャ、Run() でカスタム Stdout/Stderr で実行。タイムアウトには CommandContext を使用(プロセスをキル)。常にエラーをチェック — exec.ExitError は非ゼロ終了コードを示します。コマンドインジェクションを防ぐためユーザー入力に注意してください。

go
import "os/exec"

// Run a command and get output
cmd := exec.Command("ls", "-la", "/tmp")
output, err := cmd.Output()
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(output))

// Capture stdout and stderr separately
cmd = exec.Command("git", "status")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
fmt.Println("stdout:", stdout.String())
fmt.Println("stderr:", stderr.String())

// Pipe input to command
cmd = exec.Command("grep", "error")
cmd.Stdin = strings.NewReader("info\nerror\nwarn\n")
result, _ := cmd.Output()
fmt.Println(string(result)) // "error"

// Run with context (timeout)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd = exec.CommandContext(ctx, "sleep", "10")
err = cmd.Run() // killed after 5s
10

JSON とエンコーディング

JSON Marshal と Unmarshal

encoding/json は Go 構造体と JSON 間の変換を行います。Marshal(構造体 → JSON)、Unmarshal(JSON → 構造体)。構造体タグ(json:"name")でフィールドの命名と可視性を制御。omitempty はゼロ/空の値をスキップ。json:"-" はフィールドを完全に除外。これは Go で API リクエスト/レスポンスを処理する標準的な方法です。

go
import "encoding/json"

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email,omitempty"` // omit if empty
    Password string `json:"-"`               // never serialize
    Age      int    `json:"age,omitempty"`
}

// Struct to JSON (marshal)
user := User{ID: 1, Name: "Alice", Age: 30}
data, err := json.Marshal(user)
// {"id":1,"name":"Alice","age":30}

// Pretty print
pretty, _ := json.MarshalIndent(user, "", "  ")

// JSON to struct (unmarshal)
jsonStr := `{"id":2,"name":"Bob","email":"[email protected]"}`
var u User
err := json.Unmarshal([]byte(jsonStr), &u)
fmt.Println(u.Name) // "Bob"

// Struct tags control field names:
// json:"name"        -> rename
// json:"name,omitempty" -> skip if zero value
// json:"-"           -> skip entirely
// json:",omitempty"  -> keep name, skip if empty

マップとスライスでの JSON

動的 JSON(構造が不明)の場合、map[string]any(または interface{})にアンマーシャルします。JSON 数値は float64 になります — 型アサーションでアクセス。json.Decoder/Encoder はストリーム(ファイル、HTTP ボディ)を効率的に処理します。スキーマが分かっている場合は構造体アンマーシャリングを使用し、柔軟な/動的データにはマップを使用してください。

go
// Parse arbitrary JSON into map[string]interface{}
jsonStr := `{"name":"Alice","scores":[90,85,92],"active":true}`

var result map[string]any
json.Unmarshal([]byte(jsonStr), &result)

// Type-assert to access values
name := result["name"].(string)
scores := result["scores"].([]any)
for _, s := range scores {
    fmt.Println(s.(float64)) // JSON numbers are float64
}

// Encode a map
m := map[string]any{
    "count": 42,
    "items": []string{"a", "b", "c"},
    "meta":  map[string]any{"page": 1},
}
data, _ := json.Marshal(m)

// Decode JSON stream
dec := json.NewDecoder(strings.NewReader(jsonStr))
var v map[string]any
dec.Decode(&v)

// Encode to stream
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
enc.Encode(user)

カスタム JSON マーシャリング

カスタムシリアライズのために MarshalJSON/UnmarshalJSON を実装します。計算フィールド、代替フォーマット(Money を '99.99 USD' として)、機密データ処理、時刻フォーマットに有用です。UnmarshalJSON のメソッドレシーバは構造体を変更するためのポインタでなければなりません。time.Time は自動的に RFC 3339 文字列としてシリアライズされます。

go
// Implement MarshalJSON/UnmarshalJSON
type Money struct {
    Amount   float64
    Currency string
}

func (m Money) MarshalJSON() ([]byte, error) {
    return json.Marshal(map[string]any{
        "amount":   m.Amount,
        "currency": m.Currency,
        "display":  fmt.Sprintf("%.2f %s", m.Amount, m.Currency),
    })
}

func (m *Money) UnmarshalJSON(data []byte) error {
    var v struct {
        Amount   float64
        Currency string
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return err
    }
    m.Amount = v.Amount
    m.Currency = v.Currency
    return nil
}

money := Money{Amount: 99.99, Currency: "USD"}
data, _ := json.Marshal(money)
// {"amount":99.99,"currency":"USD","display":"99.99 USD"}

// Time formatting
type Event struct {
    Time time.Time `json:"time"`
}
// time.Time marshals as RFC 3339 by default

HTTP サーバー

net/http パッケージは HTTP サーバーを構築します。http.HandleFunc でハンドラを登録。http.ResponseWriter がレスポンスを書き込み、*http.Request がリクエストを読み取ります。JSON API の場合、Content-Type を設定し json.NewEncoder(w).Encode(data) を使用してください。標準ライブラリは本番対応です — シンプルな API にフレームワークは不要です。複雑なルーティングには mux ルータ(gorilla/mux、chi)を使用してください。

go
import (
    "encoding/json"
    "net/http"
)

type Response struct {
    Status  string `json:"status"`
    Message string `json:"message"`
}

func main() {
    // Simple handler
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
    })

    // JSON API endpoint
    http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
        user := User{ID: 1, Name: "Alice"}
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(user)
    })

    // Start server
    fmt.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

// Request methods:
// r.Method  -> "GET", "POST", etc.
// r.URL.Query().Get("key") -> query params
// r.Body    -> request body (io.ReadCloser)

HTTP クライアント

http.Get/Post は便利なショートカットです。カスタムヘッダー、メソッド、ボディには http.NewRequest + client.Do() を使用してください。接続リークを避けるため常に defer resp.Body.Close() を呼んでください。クライアントにタイムアウトを設定してください(デフォルトはタイムアウトなし — 危険)。本番では http.Client を再利用し(接続プール)、キャンセルに context を使用してください。

go
// Simple GET
resp, err := http.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
fmt.Println(string(body))

// Custom request with headers
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()

// POST with JSON body
user := User{Name: "Alice"}
body, _ := json.Marshal(user)
resp, err := http.Post(
    "https://api.example.com/users",
    "application/json",
    bytes.NewBuffer(body),
)
defer resp.Body.Close()

// With timeout
client := &http.Client{Timeout: 10 * time.Second}
resp, err = client.Get("https://slow-api.example.com")
11

テストとベンチマーク

ユニットテスト

テストファイルは _test.go で終わり、テスト関数は Test で始まります。テーブル駆動テストは慣用的なパターンです:スライスでテストケースを定義し、t.Run でサブテスト(名前付き、個別実行可能)をループします。失敗には t.Errorf(継続)、致命的失敗には t.Fatalf(停止)を使用します。'go test -v' で詳細出力を実行します。

go
// math_test.go (file must end with _test.go)
package math

import "testing"

// Test function: func TestXxx(t *testing.T)
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

// Table-driven tests (idiomatic Go)
func TestAddTable(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

// Run: go test -v
// Run specific: go test -run TestAddTable/positive

ベンチマーク

ベンチマーク関数は Benchmark で始まり *testing.B を取ります。b.N ループは安定した測定を得るためにランタイムが調整します。'go test -bench=.' で実行します。b.ResetTimer() はセットアップ時間を除外します。b.ReportAllocs() はメモリ割り当てを表示します。'benchstat' で実装を比較し、改善が有意か検証してください。

go
// Benchmark function: func BenchmarkXxx(b *testing.B)
func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// Benchmark with allocations
func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := ""
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

// Run: go test -bench=.
// Output:
// BenchmarkFibonacci-8        300     4234567 ns/op
// BenchmarkStringConcat-8     200     6789012 ns/op   100 B/op   1 allocs/op

// Benchmark with setup
func BenchmarkProcess(b *testing.B) {
    data := setup() // setup not measured
    b.ResetTimer()  // start timing here
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

// Memory allocations
func BenchmarkAllocs(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        make([]int, 100)
    }
}

テストヘルパーとモック

t.Helper() はスタックトレースでヘルパー関数をスキップし、エラーメッセージを改善します。インターフェースを実装してモック(Go のモックアプローチ — モックフレームワーク不要)。t.Cleanup() はクリーンアップ関数を登録(defer のようなものだがテストスコープ)。複雑なモックには testify/assert と mockery または gomock を使用してインターフェースからモックを生成してください。

go
// t.Helper() marks helper functions (better error locations)
func assertEqual(t *testing.T, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

// Interfaces for mocking
type DataStore interface {
    Get(key string) (string, error)
}

type MockStore struct {
    data map[string]string
}

func (m *MockStore) Get(key string) (string, error) {
    if v, ok := m.data[key]; ok {
        return v, nil
    }
    return "", errors.New("not found")
}

func TestService(t *testing.T) {
    store := &MockStore{data: map[string]string{"key": "value"}}
    svc := NewService(store)
    result := svc.GetValue("key")
    assertEqual(t, result, "value")
}

// Subtests with t.Run for setup/teardown
func TestWithCleanup(t *testing.T) {
    t.Cleanup(func() {
        // runs after test (LIFO order)
        os.Remove("tempfile")
    })
    // test code
}

標準ライブラリのハイライト

Go の標準ライブラリは包括的です。time は日付/時刻を処理(注意:フォーマットは参照時刻 2006-01-02 を使用)。regexp はパターンマッチング。context はゴルーチン間のキャンセル/タイムアウト。sync.Pool はオブジェクト再利用(GC 圧力を削減)。Go 1.21+ はジェネリックユーティリティを持つ slices と maps パッケージを追加します。

go
// time package
now := time.Now()
future := now.Add(24 * time.Hour)
formatted := now.Format("2006-01-02 15:04:05")
parsed, _ := time.Parse("2006-01-02", "2024-01-15")

// regexp
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
matches := re.FindString("date: 2024-01-15")
all := re.FindAllString("2024-01-15 and 2024-02-20", -1)

// context (cancellation, timeouts)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go doWork(ctx)
// cancel() or timeout stops doWork

// sync.Pool (reuse objects)
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)

// sort (Go 1.21+ slices package)
slices.Sort(nums)
slices.Contains(nums, 42)
slices.Reverse(nums)
12

ゴルーチンの詳細

ゴルーチンの開始と WaitGroup

ゴルーチンは Go ランタイムが管理する軽量スレッド(OS スレッドではない)です — 数十万を生成できます。sync.WaitGroup はゴルーチンの完了を調整します:開始前に Add(1)、完了時に Done()(defer で使用)、カウンタがゼロになるまで Wait() でブロック。すべてのゴルーチンが同じカウンタを共有するよう、常に WaitGroup へのポインタを渡してください。

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // signal completion when function returns
    fmt.Printf("Worker %d started\n", id)
    // ... do work ...
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // increment counter before starting goroutine
        go worker(i, &wg)
    }

    wg.Wait() // block until all goroutines call Done()
    fmt.Println("All workers finished")
}

GOMAXPROCS とスケジューリング

GOMAXPROCS はゴルーチンを同時に実行する OS スレッド数を制御します — デフォルトは CPU コア数で、ほぼ常に最適です。Go は M:N スケジューリングを使用:少数の OS スレッド上で多数のゴルーチン。ブロッキング I/O やチャネル操作により、スケジューラは同じスレッドで他のゴルーチンを実行します。runtime.Gosched() で明示的に譲渡。GOMAXPROCS をチューニングする必要はほぼありません。

go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // GOMAXPROCS: number of OS threads available to run goroutines
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // defaults to CPU cores

    // Set it manually (rarely needed — default is optimal)
    runtime.GOMAXPROCS(2)

    // Goroutines are multiplexed onto OS threads (M:N scheduling)
    // - Blocking syscalls (I/O) don't block other goroutines
    // - The scheduler steals work across threads
    // - A goroutine blocked on channel/IO yields its thread

    // Number of goroutines currently running
    go func() { runtime.Gosched() }() // yield to other goroutines
    fmt.Println("Goroutines:", runtime.NumGoroutine())
}

ゴルーチンリークと防止

ゴルーチンリークはゴルーチンが永遠にブロックした時に発生します(例:誰も読まないバッファなしチャネルへの送信) — ガベージコレクションされません。以下でリークを防止:チャネルのバッファリング、キャンセルチャネル/context との select、常に終了パスの提供。リークしたゴルーチンはメモリと CPU を蓄積します。本番でのリーク検出には runtime.NumGoroutine() と pprof を使用してください。

go
package main

// BAD: goroutine leaks — this goroutine never exits
func leaky() <-chan int {
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks forever if nobody reads from ch
    }()
    return ch
}

// GOOD: use context for cancellation
func safe(ctx context.Context) <-chan int {
    ch := make(chan int, 1) // buffered — sender never blocks
    go func() {
        select {
        case ch <- 42:
        case <-ctx.Done(): // exit when context is cancelled
            return
        }
    }()
    return ch
}

// GOOD: always ensure senders can exit
// 1. Buffer the channel so send doesn't block
// 2. Use select with a done/cancel channel
// 3. Use context.WithCancel/WithTimeout for lifecycle control

sync.Once(一回限りの初期化)

sync.Once はすべてのゴルーチン間で関数が正確に1回実行されることを保証します — スレッドセーフなシングルトンと遅延初期化の標準的な方法です。ミューテックスで保護されたフラグチェックより効率的です。Do メソッドは最初の呼び出しが完了するまで並行呼び出し元をブロックします。sync.Once は sync.OnceValue(Go 1.21+)などの多くの標準ライブラリパターンでも内部的に使用されています。

go
package main

import (
    "fmt"
    "sync"
)

var (
    instance *Database
    once     sync.Once
)

type Database struct{ name string }

func GetDB() *Database {
    // sync.Once guarantees the function runs exactly once,
    // even if called from many goroutines simultaneously
    once.Do(func() {
        instance = &Database{name: "production"}
        fmt.Println("Database initialized")
    })
    return instance
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            GetDB() // "Database initialized" prints only once
        }()
    }
    wg.Wait()
}

ゴルーチンによるワーカープール

ワーカープールパターンは並行度を固定数のゴルーチンに制限し、チャネルからのジョブを処理します。これにより無制限のゴルーチン生成によるリソース枯渇を防ぎます。ワーカーは 'for job := range jobs' を使用し、チャネルがクローズされると終了します。close(jobs) がすべてのワーカーに停止をシグナルします。これは有界並行性の標準的な Go 並行パターンです。

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs { // exits when jobs channel is closed
        results <- job * job // process and send result
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    // Start 3 workers
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs) // signal workers to stop (range loop exits)

    wg.Wait()
    close(results)

    for r := range results {
        fmt.Println("Result:", r)
    }
}
13

チャネルと Select

チャネルの基礎(バッファなしとバッファ付き)

バッファなしチャネル(make(chan T))は送信側と受信側を同期します — 送信は受信側が準備できるまでブロックします。バッファ付きチャネル(make(chan T, n))は受信側なしで n 回送信を許可し、送信側/受信側のタイミングを分離します。チャネルをクローズするのは送信側のみ(「これ以上値がない」をシグナル)。クローズされたチャネルからの受信は ok=false でゼロ値を返します。range ループはチャネルがクローズされると終了します。

go
package main

import "fmt"

func main() {
    // Unbuffered: send blocks until someone receives (synchronous)
    ch := make(chan int)
    go func() {
        ch <- 42 // blocks until main reads
    }()
    fmt.Println(<-ch) // 42

    // Buffered: send blocks only when buffer is full
    buf := make(chan int, 3)
    buf <- 1 // doesn't block (buffer has space)
    buf <- 2
    buf <- 3
    // buf <- 4 // would block — buffer full
    fmt.Println(<-buf) // 1 (FIFO)

    // Close a channel (sender closes, never the receiver)
    close(buf)

    // Check if closed (ok is false when closed and empty)
    val, ok := <-buf
    fmt.Println(val, ok) // 2 true

    // Range over channel until closed
    nums := make(chan int, 3)
    nums <- 10; nums <- 20; nums <- 30
    close(nums)
    for n := range nums {
        fmt.Println(n) // 10, 20, 30
    }
}

Select 文

select はゴルーチンが複数のチャネル操作を同時に待機できるようにします — 準備できた最初のケースを選びます(複数準備済みの場合はランダム)。default ケースで select を非ブロックにします。time.After はタイムアウトチャネルを作成します。select は Go の並行調整の心臓部です:マルチプレクシング、タイムアウト、キャンセル、fan-in/fan-out パターンすべてがこれに基づきます。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()

    // select waits on multiple channel operations
    // picks the first one that's ready (random if multiple ready)
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }

    // Non-blocking receive with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("no message ready") // runs immediately
    }

    // Timeout with time.After
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case <-time.After(500 * time.Millisecond):
        fmt.Println("timed out")
    }
}

Fan-In と Fan-Out パターン

Fan-out は並行性のために複数のゴルーチンに作業を分散し、fan-in は複数のチャネルを1つにマージします。これらを組み合わせて並行パイプラインを形成します:fan-out ワーカーが独立して処理し、fan-in が結果を収集します。fan-in は WaitGroup を使用して、すべての入力チャネルが枯渇した後にのみマージチャネルをクローズします。これらのパターンは Go の並行データ処理の基盤です。

go
package main

import (
    "fmt"
    "sync"
)

// Fan-Out: distribute work across multiple goroutines
func fanOut(input <-chan int, n int) []<-chan int {
    outputs := make([]<-chan int, n)
    for i := 0; i < n; i++ {
        outputs[i] = process(input) // each worker reads from same input
    }
    return outputs
}

func process(input <-chan int) <-chan int {
    output := make(chan int)
    go func() {
        defer close(output)
        for v := range input {
            output <- v * v // transform
        }
    }()
    return output
}

// Fan-In: merge multiple channels into one
func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    output := func(c <-chan int) {
        defer wg.Done()
        for v := range c {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()
    return merged
}

方向チャネル(送信/受信専用)

方向チャネル型(受信専用 <-chan T、送信専用 chan<- T)はコンパイル時にチャネルの使用を強制します。双方向チャネルは関数に渡される際、方向型に暗黙的に変換されます。これにより意図を文書化しバグを防止します — プロデューサ関数は自身の出力チャネルから受信できません。関数シグネチャで方向型を使用して契約を明確にしてください。

go
package main

import "fmt"

// <-chan int: receive-only (can read, cannot send)
// chan<- int: send-only (can send, cannot read)
func producer(out chan<- int) { // can only send
    for i := 0; i < 3; i++ {
        out <- i
    }
    close(out)
}

func consumer(in <-chan int) { // can only receive
    for v := range in {
        fmt.Println("consumed:", v)
    }
}

func main() {
    ch := make(chan int)

    // Directional restrictions are enforced at compile time
    go producer(ch) // bidirectional chan converts to send-only
    consumer(ch)    // bidirectional chan converts to receive-only

    // This prevents bugs: producer can't accidentally read,
    // consumer can't accidentally send or close
}

Ticker と Timer チャネル

time.Ticker はインターバルで繰り返し発火します — select で ticker.C をチャネルとして使用して定期タスクに使用します。time.Timer は期間後に一度発火します。リソースを解放しリークを避けるため、常に Stop() を呼んでください。time.After はチャネルを返す便利なワンショットタイマーですがキャンセルできないため、select ループでは蓄積を避けるため NewTimer を優先してください。Reset でタイマーを再スケジュールできます。

go
package main

import (
    "fmt"
    "time"
)

func main() {
    // Ticker: fires repeatedly at intervals
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop() // always stop to release resources

    go func() {
        for t := range ticker.C { // ticker.C is a channel
            fmt.Println("Tick at", t)
        }
    }()

    // Timer: fires once after a duration
    timer := time.NewTimer(2 * time.Second)
    <-timer.C // blocks until timer fires
    fmt.Println("Timer fired!")

    // time.After: one-shot timer as a channel (no Stop needed)
    select {
    case <-time.After(1 * time.Second):
        fmt.Println("1 second elapsed")
    }

    // Reset a timer (cancel and reschedule)
    timer2 := time.NewTimer(5 * time.Second)
    timer2.Reset(100 * time.Millisecond)
    <-timer2.C
}
14

Context パッケージ

context.WithCancel と WithTimeout

context.Context はキャンセル、タイムアウト、リクエストスコープの値をゴルーチン境界を越えて運びます。WithCancel は context とキャンセル関数を返し、WithTimeout は期間後に自動キャンセルします。ゴルーチンは select で ctx.Done()(チャネル)をチェックして停止タイミングを知ります。タイムアウトが発火しても常にキャンセル関数を呼び出してください(defer で) — そうしないと context がリークします。

go
package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done(): // cancelled or timed out
            fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d working...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    // WithCancel: manual cancellation
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx, 1)
    time.Sleep(2 * time.Second)
    cancel() // stop the worker
    time.Sleep(500 * time.Millisecond)

    // WithTimeout: auto-cancel after duration
    ctx2, cancel2 := context.WithTimeout(context.Background(), 1500*time.Millisecond)
    defer cancel2() // always call cancel to release resources
    go worker(ctx2, 2)
    time.Sleep(2 * time.Second) // worker stops after 1.5s
}

呼び出しチェーンでの Context の伝播

Context は I/O を行うすべての関数の最初のパラメータであるべきで、呼び出しチェーン全体に伝播されるべきです。http.Request.Context() はクライアントが切断した時に自動的にキャンセルされます。データベース/HTTP 操作に ctx を渡す(QueryRowContext、NewRequestWithContext)と、context キャンセル時に中断されます — 無駄な作業とリソースリークを防ぎます。context を構造体に格納しないでください。

go
package main

import (
    "context"
    "database/sql"
    "net/http"
)

// Context should be the FIRST parameter, named ctx
func GetUser(ctx context.Context, db *sql.DB, id int) (string, error) {
    // Pass ctx to all blocking operations so they cancel together
    var name string
    err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id=?", id).Scan(&name)
    return name, err
}

func handler(w http.ResponseWriter, r *http.Request) {
    // r.Context() is cancelled when the client disconnects
    ctx := r.Context()

    name, err := GetUser(ctx, db, 42)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    w.Write([]byte(name))
}

// BEST PRACTICE: pass context through every function in the call chain
// that might do I/O. This ensures a client disconnect or timeout
// cancels ALL in-flight work (DB queries, HTTP calls, etc.)

Context 値(リクエストスコープデータ)

context.WithValue は呼び出しチェーンを流れるリクエストスコープのデータ(ユーザー ID、トレース ID、認証トークンなど)を格納します。キー衝突を避けるためカスタムキー型(文字列ではなく)を使用してください。値は関数パラメータではなくリクエストが必要とするデータであるべきです — Go チームは控えめに使用し、主にトレース/認証のような横断的関心事に使用することを推奨しています。値の取得時に常に型アサーションしてください。

go
package main

import (
    "context"
    "fmt"
    "net/http"
)

// Define a custom key type to avoid collisions
type contextKey string

const userIDKey contextKey = "userID"

// Set a value in context
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := 42 // authenticate...
        // WithValue creates a new context with the value
        ctx := context.WithValue(r.Context(), userIDKey, userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Get a value from context (must type-assert)
func getUserID(ctx context.Context) (int, bool) {
    if v, ok := ctx.Value(userIDKey).(int); ok {
        return v, true
    }
    return 0, false
}

func handler(w http.ResponseWriter, r *http.Request) {
    if uid, ok := getUserID(r.Context()); ok {
        fmt.Fprintf(w, "User ID: %d", uid)
    }
}

Context によるグレースフルシャットダウン

グレースフルシャットダウンはサーバー終了前に処理中のリクエストを完了させます。signal.Notify で OS シグナル(Ctrl+C、コンテナオーケストレータからの SIGTERM)をキャプチャします。server.Shutdown(ctx) は新規接続の受け入れを停止し、アクティブな接続の完了を待ちます(context タイムアウトまで)。本番サーバーに不可欠です — ないとアクティブリクエストが abruptly 終了され、エラーやデータ破損を引き起こします。

go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    server := &http.Server{Addr: ":8080", Handler: mux}

    // Listen for OS interrupt signals (Ctrl+C, SIGTERM)
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        log.Println("Server starting on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    <-stop // block until signal received
    log.Println("Shutting down...")

    // Give in-flight requests 10 seconds to finish
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Forced shutdown: %v", err)
    }
    log.Println("Server stopped gracefully")
}

Context のデッドラインとエラー処理

context.WithDeadline は絶対時刻でキャンセルし、WithTimeout は相対期間でキャンセルします(WithTimeout は WithDeadline(now+timeout) と同じ)。ctx.Err() は context.DeadlineExceeded または context.Canceled を返し、停止理由を区別できます。context エラーのチェックには errors.Is() を使用してください。長い操作の開始時に常に ctx.Err() をチェックし、ブロッキング待機中は select で ctx.Done() を使用してください。

go
package main

import (
    "context"
    "errors"
    "fmt"
    "time"
)

func slowOperation(ctx context.Context) (string, error) {
    // Check if already cancelled before starting
    if err := ctx.Err(); err != nil {
        return "", err
    }

    select {
    case <-time.After(3 * time.Second): // simulate slow work
        return "result", nil
    case <-ctx.Done(): // cancelled or deadline exceeded
        return "", ctx.Err()
    }
}

func main() {
    // Deadline: cancel at a specific time
    deadline := time.Now().Add(1 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    result, err := slowOperation(ctx)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            fmt.Println("Operation timed out")
        } else if errors.Is(err, context.Canceled) {
            fmt.Println("Operation was cancelled")
        }
        return
    }
    fmt.Println(result)
}
15

HTTP サーバーとクライアント

HTTP サーバー(net/http)

net/http は本番対応の HTTP サーバーを提供します。http.HandleFunc でパスごとにハンドラを登録します。json.NewEncoder(w).Encode() で JSON レスポンスを書き込み、json.NewDecoder(r.Body).Decode() でリクエストボディを解析します。常に Content-Type ヘッダーを設定し r.Method をチェックしてください。http.Error でエラーステータスを送信します。ListenAndServe でサーバーを開始し、log.Fatal でラップしてエラーをキャッチします。

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) {
    user := User{ID: 1, Name: "Alice"}

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    user.ID = 99
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func main() {
    http.HandleFunc("/users/1", getUser)
    http.HandleFunc("/users", createUser)

    log.Println("Server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

HTTP クライアントとリクエスト

デフォルトの http.Client にはタイムアウトがありません — 応答しないサーバーで永遠にハングするのを避けるため常に設定してください。キャンセル/タイムアウトのために http.NewRequestWithContext で context をアタッチします。接続リークを避けるため常に defer resp.Body.Close() を呼んでください。本番では単一の http.Client を再利用してください(接続プールを管理します)。http.Get はショートカットですがタイムアウトとカスタマイズがありません。

go
package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

func main() {
    // Simple GET
    resp, err := http.Get("https://api.example.com/users")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // ALWAYS close the body

    var users []map[string]any
    json.NewDecoder(resp.Body).Decode(&users)
    fmt.Println(users)

    // Custom client with timeout (default has NO timeout!)
    client := &http.Client{Timeout: 30 * time.Second}

    // POST with JSON body and context
    body, _ := json.Marshal(map[string]string{"name": "Bob"})
    req, _ := http.NewRequestWithContext(context.Background(),
        "POST", "https://api.example.com/users", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")

    resp, err = client.Do(req)
    defer resp.Body.Close()
    fmt.Println("Status:", resp.Status)
}

ミドルウェアパターン

ミドルウェアはハンドラをラップして横断的関心事(ロギング、認証、CORS、レート制限)を追加します。func(http.Handler) http.Handler シグネチャが標準的なミドルウェア型です。Chain は順序に従って適用します(最外側が最初)。このパターンは Chi、Echo、Gin などのフレームワークの基盤です。ResponseWriter をラップしてステータスコードをキャプチャしロギングできます。

go
package main

import (
    "log"
    "net/http"
    "time"
)

// Middleware wraps an http.Handler to add cross-cutting concerns
type Middleware func(http.Handler) http.Handler

func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r) // call the wrapped handler
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func Auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Chain multiple middlewares
func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello!"))
    })
    // Apply middlewares: Auth -> Logging -> handler
    http.Handle("/", Chain(handler, Logging, Auth))
    http.ListenAndServe(":8080", nil)
}

静的ファイルとテンプレートの提供

http.FileServer はディレクトリから静的ファイルを提供し、http.StripPrefix はパスプレフィックスを削除してファイルパスが正しく解決されるようにします。html/template は HTML を安全にレンダリングします(XSS を防ぐ自動エスケープ)。template.Must は解析エラーでパニックします(起動時には問題ありません)。テンプレートはデータに {{.Field}}、反復に {{range}} を使用します。本番ではディスクから読む代わりに go:embed でファイルを埋め込むことを検討してください。

go
package main

import (
    "html/template"
    "net/http"
)

func main() {
    // Serve static files (CSS, JS, images)
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    // HTML templates
    tmpl := template.Must(template.ParseFiles("templates/index.html"))

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        data := struct {
            Title string
            Items []string
        }{
            Title: "My Page",
            Items: []string{"Apple", "Banana", "Cherry"},
        }
        tmpl.Execute(w, data)
    })

    http.ListenAndServe(":8080", nil)
}

// templates/index.html:
// <h1>{{.Title}}</h1>
// <ul>{{range .Items}}<li>{{.}}</li>{{end}}</ul>

グレースフルシャットダウンと go:embed

go:embed はビルド時にファイルをコンパイル済みバイナリにバンドルします — 外部ファイル依存なしの真のシングルバイナリデプロイを可能にします。var 宣言の上に //go:embed ディレクティブを使用します。embed.FS は読み取り専用の仮想ファイルシステムです。fs.Sub でサブファイルシステムを作成します(ディレクトリプレフィックスを削除)。静的アセット、HTML テンプレート、SQL マイグレーション、設定ファイルなどに使用できます。

go
package main

import (
    "embed"
    "io/fs"
    "net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
    // Embed static files into the binary (no external files needed)
    // Access the embedded filesystem
    sub, _ := fs.Sub(staticFiles, "static")
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))

    // go:embed benefits:
    // - Single binary deployment (no external files)
    // - Files are compiled into the executable
    // - Works with templates, configs, migrations, etc.

    // Embed a single file
    //go:embed config.json
    // var configData []byte

    // Embed with pattern
    //go:embed templates/*.html
    // var templates embed.FS

    http.ListenAndServe(":8080", nil)
}
16

Sync パッケージ

sync.Mutex と sync.RWMutex

sync.Mutex は排他ロックを提供します — 一度に1つのゴルーチンのみが保持できます。sync.RWMutex は複数の並行リーダーまたは1つの排他ライターを許可します — 読み取りが書き込みをはるかに上回る場合に使用します。関数がパニックした場合のデッドロックを防ぐため、常に Lock を defer Unlock とペアにしてください。ミューテックスは構造体に埋め込んで(小文字の mu)プライベートに保ちます。ミューテックスは絶対にコピーしないでください(常にポインタを使用)。

go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // always unlock with defer
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex: allows multiple readers OR one writer
type SafeCache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *SafeCache) Get(key string) (string, bool) {
    c.mu.RLock()         // read lock (multiple readers OK)
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

func (c *SafeCache) Set(key, val string) {
    c.mu.Lock()          // write lock (exclusive)
    defer c.mu.Unlock()
    c.data[key] = val
}

sync.Map(並行マップ)

sync.Map は特定のシナリオに最適化された並行セーフなマップです:一度書いて何度も読む(キャッシュ)、またはゴルーチン間でキーが重複しないアクセス。アトミック操作により読み取りでロックを回避します。ただし、一般的な並行マップには sync.RWMutex で保護された通常のマップの方が速く使いやすいことが多いです。sync.Map の API はキーと値に any(interface{})を使用するため、型安全性が失われます。

go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // Store and Load (thread-safe, no locks needed)
    m.Store("name", "Alice")
    m.Store("age", 30)

    val, ok := m.Load("name")
    fmt.Println(val, ok) // Alice true

    // LoadOrStore: atomic get-or-set
    actual, loaded := m.LoadOrStore("name", "Bob")
    fmt.Println(actual, loaded) // Alice true (already existed)

    // Delete
    m.Delete("age")

    // Range over all entries
    m.Range(func(key, value any) bool {
        fmt.Printf("%v = %v\n", key, value)
        return true // continue; return false to stop
    })

    // Use sync.Map when:
    // 1. Keys are written once, read many times (caches)
    // 2. Multiple goroutines read/write disjoint keys
    // For most cases, a regular map + Mutex is simpler and faster
}

sync.Cond(条件変数)

sync.Cond は条件変数を実装します — ゴルーチンは条件が真になるのを待ちます。Wait() はロックを原子的に解放してスリープし、Signal() は1つの待機者を起こし、Broadcast() は全てを起こします。スプリアスウェイクアップを処理するため、Wait() の周りには(if ではなく)for ループを使用してください。Cond はプロデューサー-コンシューマーキューや状態変化の待機に有用ですが、チャネルの方がシンプルな代替となることが多いです。

go
package main

import (
    "fmt"
    "sync"
    "time"
)

type Queue struct {
    items []int
    mu    sync.Mutex
    cond  *sync.Cond
}

func NewQueue() *Queue {
    q := &Queue{}
    q.cond = sync.NewCond(&q.mu) // cond is tied to the mutex
    return q
}

func (q *Queue) Put(item int) {
    q.mu.Lock()
    defer q.mu.Unlock()
    q.items = append(q.items, item)
    q.cond.Signal() // wake one waiting goroutine
}

func (q *Queue) Get() int {
    q.mu.Lock()
    defer q.mu.Unlock()
    for len(q.items) == 0 {
        q.cond.Wait() // atomically unlocks mu, sleeps, re-locks on wake
    }
    item := q.items[0]
    q.items = q.items[1:]
    return item
}

func main() {
    q := NewQueue()
    go func() {
        time.Sleep(1 * time.Second)
        q.Put(42)
    }()
    fmt.Println("Waiting for item...")
    fmt.Println("Got:", q.Get()) // blocks until item available
}

sync.Pool(オブジェクト再利用)

sync.Pool はオブジェクトを再利用してアロケーションと GC 負荷を削減します — bytes.Buffer のような頻繁にアロケートされる短命オブジェクトに最適です。Get() はプールされたオブジェクトを返し(空なら New を呼び出し)、Put() は返却します。プールは GC 中にクリアされるため、永続化に依存しないでください。再利用前に必ずオブジェクトをリセットしてください。標準ライブラリは sync.Pool を広範に使用しています(http、json、fmt)。

go
package main

import (
    "bytes"
    "sync"
)

var bufPool = sync.Pool{
    New: func() any {
        return new(bytes.Buffer) // create when pool is empty
    },
}

func Process(data []byte) string {
    // Get a buffer from the pool (or create via New)
    buf := bufPool.Get().(*bytes.Buffer)
    defer bufPool.Put(buf) // return it when done

    buf.Reset() // clear before reuse
    buf.Write(data)
    buf.WriteString("-processed")
    return buf.String()
}

// Benefits:
// - Reduces GC pressure by reusing objects
// - Avoids allocation overhead for short-lived objects
// - Pool entries can be garbage collected between GC cycles
//
// Use for: bytes.Buffer, json encoders, temporary slices
// NOT for: long-lived objects or when size is unpredictable

errgroup(エラー伝播付きグループ)

errgroup(golang.org/x/sync から)は最初のエラーを返し、context 経由で残りのゴルーチンをキャンセルできる WaitGroup です。g.Go() はゴルーチンを開始し、g.Wait() はブロックし、最初の非 nil エラーを返します。WithContext はいずれかのゴルーチンがエラーを返した時にキャンセルされる context を作成します — これにより他のゴルーチンが早期に停止します。これは全てが成功または失敗すべき並行操作を実行する慣用的な方法です。

go
package main

import (
    "context"
    "fmt"
    "golang.org/x/sync/errgroup"
    "net/http"
)

// errgroup is like WaitGroup but propagates the first error
// and can cancel remaining goroutines on failure
func fetchAll(urls []string) ([]*http.Response, error) {
    g, ctx := errgroup.WithContext(context.Background())
    results := make([]*http.Response, len(urls))

    for i, url := range urls {
        i, url := i, url // capture loop variables
        g.Go(func() error {
            req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                return err // cancels ctx, stopping other goroutines
            }
            results[i] = resp
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err // first error returned
    }
    return results, nil
}

func main() {
    urls := []string{
        "https://example.com",
        "https://golang.org",
    }
    resps, err := fetchAll(urls)
    fmt.Println(len(resps), err)
}
17

ジェネリクス(Go 1.18+)

ジェネリック関数と型パラメータ

Go ジェネリクス(1.18+)は角括弧内で型パラメータを使用します:[T any] は T を任意の型として宣言します。コンパイラが引数から型を推論するため、明示的に指定することは稀です。ジェネリクスにより、interface{} と型アサーションなしで Map/Filter/Reduce のような型安全な再利用可能関数が可能になります。'any' はジェネリクスと共に導入された interface{} のエイリアスです。

go
package main

import "fmt"

// Type parameters in brackets: [T any] means T can be any type
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    squared := Map(nums, func(n int) int { return n * n })
    fmt.Println(squared) // [1 4 9 16 25]

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4]

    sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(sum) // 15

    // Works with any type
    words := []string{"go", "rust", "python"}
    lengths := Map(words, func(s string) int { return len(s) })
    fmt.Println(lengths) // [2 4 6]
}

型制約と comparable

型制約はジェネリックが受け入れる型を制限します。'comparable' は == をサポートする型の組み込み制約です(マップキーと比較に必要)。カスタム制約は型ユニオン(int | float64)を使用して特定の型を許可します。golang.org/x/exp/constraints パッケージは Ordered(<、> など用)を提供します。制約は型セットとメソッドを組み合わせできます。

go
package main

import "fmt"

// comparable: types that support == and != (built-in constraint)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// Custom constraint with a type set
type Number interface {
    int | int64 | float64 | float32
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// Multiple type parameters
func Pair[T, U any](first T, second U) struct{ First T; Second U } {
    return struct{ First T; Second U }{first, second}
}

func main() {
    fmt.Println(Contains([]string{"a", "b", "c"}, "b")) // true
    fmt.Println(Contains([]int{1, 2, 3}, 5))             // false

    fmt.Println(Sum([]int{1, 2, 3}))           // 6
    fmt.Println(Sum([]float64{1.5, 2.5}))      // 4

    p := Pair("Alice", 30)
    fmt.Println(p) // {Alice 30}
}

ジェネリックデータ構造

ジェネリック型(Stack[T any])は interface{} のボックス化や型アサーションなしで任意の型で動作する型安全なデータ構造を作成します。型パラメータ T は型の一部であるため、Stack[int] と Stack[string] は異なるコンパイル時チェック付きの型です。これによりランタイムの型エラーのクラス全体が排除されます。ジェネリック構造体、メソッド、インターフェースはすべて型パラメータをサポートします。

go
package main

import "fmt"

// Generic Stack — works with any type, type-safe
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    index := len(s.items) - 1
    item := s.items[index]
    s.items = s.items[:index]
    return item, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

func main() {
    // Type-safe stack of ints
    intStack := &Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    val, _ := intStack.Pop()
    fmt.Println(val) // 2

    // Type-safe stack of strings
    strStack := &Stack[string]{}
    strStack.Push("hello")
    s, _ := strStack.Pop()
    fmt.Println(s) // hello

    // intStack.Push("oops") // compile error! type-safe
}

~ によるジェネリック制約(基となる型)

制約内の ~ 接頭辞は、基となる型が名前付き型である任意の型にマッチします — したがって ~string は string と type MyString string の両方にマッチします。~ がない場合、制約は正確な名前付き型にのみマッチし、カスタム型にはほとんど役立ちません。ジェネリクスを型エイリアスやプリミティブから派生した名前付き型で動作させたい場合(ドメインモデリングで一般的)に ~ を使用してください。

go
package main

import "fmt"

// ~ allows any type whose UNDERLYING type matches
// (e.g., type MyString string would match ~string)
type StringLike interface {
    ~string
}

func Join[S StringLike](items []S, sep string) string {
    result := ""
    for i, item := range items {
        if i > 0 {
            result += sep
        }
        result += string(item) // convert underlying type to string
    }
    return result
}

// Numeric constraint with ~
type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

func Double[T Integer](v T) T {
    return v * 2
}

// Custom type based on int
type Score int

func main() {
    type MyString string
    items := []MyString{"a", "b", "c"}
    fmt.Println(Join(items, "-")) // a-b-c

    s := Double(Score(21))
    fmt.Println(s) // 42
}

ジェネリクス vs インターフェース(使用すべき時)

アルゴリズムが型間で同一の場合(コレクション、数学、変換)はジェネリクスを選択してください — コンパイル時の型安全性をランタイムオーバーヘッドなしで提供します。異なる型が異なる実装を必要とする場合(ポリモーフィズム)やランタイムディスパッチが必要な場合(依存性注入、モック)はインターフェースを選択してください。これらは補完的です:ジェネリクスはインターフェースで制約でき(T Stringer)、両方の長所を享受できます。

go
package main

import "fmt"

// GENERICS: best for algorithms/data structures that work
// the same way regardless of type (collections, sorting, mapping)
func Max[T int | float64](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// INTERFACES: best when different types need DIFFERENT implementations
// (polymorphism, dependency injection, mocking)
type Stringer interface {
    String() string
}

type Dog struct{ Name string }
func (d Dog) String() string { return "Dog: " + d.Name }

type Cat struct{ Name string }
func (c Cat) String() string { return "Cat: " + c.Name }

func Print[T Stringer](v T) {
    fmt.Println(v.String())
}

func main() {
    fmt.Println(Max(3, 7))       // 7
    fmt.Println(Max(3.14, 2.71)) // 3.14

    Print(Dog{"Rex"}) // Dog: Rex
    Print(Cat{"Whiskers"}) // Cat: Whiskers
}

// RULE OF THUMB:
// - Same logic, different types -> Generics
// - Different logic, shared contract -> Interfaces
// - Need runtime polymorphism -> Interfaces
// - Need compile-time type safety -> Generics
18

リフレクション

reflect.Type と reflect.Value

reflect.TypeOf はランタイム型を返し、reflect.ValueOf はランタイム値を返します。Type から構造体フィールド、メソッド、タグを検査できます。Value から読み取り、(CanSet で)フィールドを変更できます。リフレクションは強力ですが遅く(直接アクセスの10-100倍)、コンパイル時の型安全性をバイパスします — 主にシリアライズ、ORM、フレームワークで控えめに使用してください。

go
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    u := User{Name: "Alice", Age: 30}

    // Get type and value at runtime
    t := reflect.TypeOf(u)
    v := reflect.ValueOf(u)

    fmt.Println("Type:", t.Name())        // User
    fmt.Println("Kind:", t.Kind())         // struct
    fmt.Println("NumFields:", t.NumField())

    // Iterate struct fields
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        tag := field.Tag.Get("json")
        fmt.Printf("%s (%s) = %v [json:%s]\n",
            field.Name, field.Type, value, tag)
    }
    // Name (string) = Alice [json:name]
    // Age (int) = 30 [json:age]
}

リフレクションによる値の変更

リフレクション経由で値を変更するには、ポインタを渡して .Elem() でデリファレンスする必要があります。CanSet() はフィールドが代入可能か(アドレス可能な値のエクスポートされたフィールド)を報告します。非エクスポート(小文字)フィールドは読み取り可能ですが設定できません。SetInt/SetString/Set はランタイムで型チェックされ、不一致の場合パニックします。リフレクションベースの変更は設定パーサーや ORM の基盤です。

go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    type Config struct {
        Port int
        Host string
    }

    cfg := Config{Port: 8080, Host: "localhost"}

    // MUST pass a pointer to modify (ValueOf of a struct is a copy)
    v := reflect.ValueOf(&cfg).Elem()

    // Check if a field is settable
    portField := v.FieldByName("Port")
    fmt.Println("Settable:", portField.CanSet()) // true

    // Modify fields by name
    portField.SetInt(9090)
    v.FieldByName("Host").SetString("0.0.0.0")

    fmt.Println(cfg) // {9090 0.0.0.0}

    // Set via interface (type-checked at runtime)
    v.FieldByName("Port").Set(reflect.ValueOf(7070))
    fmt.Println(cfg) // {7070 0.0.0.0}

    // Unexported fields are NOT settable (CanSet returns false)
}

メソッドの動的呼び出し

リフレクションは名前でメソッドを動的に呼び出せます — RPC フレームワーク、プラグインシステム、ルーティングに有用です。MethodByName は Value を返し、Call() は reflect.Value 引数のスライスで呼び出し、結果のスライスを返します。NumMethod/Method はメソッドを列挙します。リフレクション経由のメソッド呼び出しは遅く型安全性をバイパスするため、メソッド名がコンパイル時に不明な場合のみ使用してください。

go
package main

import (
    "fmt"
    "reflect"
)

type Calculator struct{}

func (c Calculator) Add(a, b int) int { return a + b }
func (c Calculator) Mul(a, b int) int { return a * b }

func main() {
    calc := Calculator{}
    v := reflect.ValueOf(calc)

    // Find a method by name
    method := v.MethodByName("Add")
    fmt.Println("Found:", method.IsValid()) // true

    // Call with arguments (each arg must be a reflect.Value)
    args := []reflect.Value{
        reflect.ValueOf(3),
        reflect.ValueOf(4),
    }
    results := method.Call(args)
    fmt.Println("3 + 4 =", results[0].Int()) // 7

    // List all methods
    t := reflect.TypeOf(calc)
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("Method: %s, Type: %v\n", m.Name, m.Type)
    }
    // Method: Add, Type: func(main.Calculator, int, int) int
    // Method: Mul, Type: func(main.Calculator, int, int) int
}

実例:構造体からマップへ(タグベース)

このパターン — 構造体フィールドを反復してタグを読み取る — は encoding/json、YAML パーサー、ORM、バリデーションライブラリが内部で動作する方法です。reflect によりタグに基づいて任意の構造体を処理するジェネリックコードを書けます。json:'-' タグ規約(このフィールドをスキップ)は標準です。これは Go におけるリフレクションの最も正当な使用法の一つです。

go
package main

import (
    "fmt"
    "reflect"
)

// Convert any struct to a map using struct tags as keys
func StructToMap(obj any, tag string) map[string]any {
    result := make(map[string]any)
    v := reflect.ValueOf(obj)
    t := reflect.TypeOf(obj)

    // Dereference pointers
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
        t = t.Elem()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        // Use the tag value if present, else the field name
        key := field.Tag.Get(tag)
        if key == "" {
            key = field.Name
        }
        // Skip fields tagged with "-"
        if key == "-" {
            continue
        }
        result[key] = v.Field(i).Interface()
    }
    return result
}

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Pass string `json:"-"` // excluded
}

func main() {
    u := User{Name: "Alice", Age: 30, Pass: "secret"}
    m := StructToMap(u, "json")
    fmt.Println(m) // map[age:30 name:Alice]
}
19

ビルドとツール

go build、go run と go install

go build は実行可能ファイルにコンパイルし、go run はテンポラリファイルにコンパイルして実行し(開発に最適)、go install はバイナリを $GOPATH/bin に配置してグローバルアクセスを可能にします。Go の際立つ特徴は GOOS/GOARCH による簡単なクロスコンパイルです — ツールチェーンが不要です。-ldflags='-s -w' はデバッグ情報を削除し、約30%小さいバイナリにします。-X はビルド時に値(バージョン文字列など)を注入します — CI/CD に便利です。

go
# Build an executable (outputs to current directory)
go build -o myapp              # custom output name
go build -o bin/myapp ./cmd    # build a specific package

# Build for different OS/arch (cross-compilation)
GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-mac  # Apple Silicon

# Run without building an executable (compiles to temp, runs, deletes)
go run main.go                 # run single file
go run ./cmd/server            # run a package

# Install: builds and puts binary in $GOPATH/bin
go install ./cmd/myapp         # now 'myapp' is on your PATH

# Build flags
go build -v                    # verbose: print packages being compiled
go build -race                 # enable race detector (for testing)
go build -ldflags="-s -w"      # strip debug info (smaller binary)
go build -ldflags="-X main.Version=1.0.0"  # inject version at build time

go mod(モジュール管理)

go mod は go.mod(依存関係リスト)と go.sum(セキュリティ用チェックサム)で依存関係を管理します。go get は依存関係を追加/アップグレードし、go mod tidy はファイルを同期します(コミット前に実行)。Go はセマンティックインポートバージョニングを使用します:v2+ は /v2 パスサフィックスが必要です。モジュールキャッシュは $GOPATH/pkg/mod で共有されます。go mod vendor は再現可能なオフラインまたは監査済みビルド用の vendor/ ディレクトリを作成します。

go
# Initialize a new module
go mod init github.com/user/myproject

# This creates go.mod:
# module github.com/user/myproject
# go 1.21

# Add a dependency (auto-added to go.mod)
go get github.com/gin-gonic/gin@latest
go get github.com/lib/[email protected]  # specific version

# Tidy: add missing deps, remove unused ones
go mod tidy

# Download dependencies to local cache
go mod download

# Verify dependencies haven't been modified
go mod verify

# Vendor: copy deps into ./vendor (for offline builds)
go mod vendor

# Upgrade dependencies
go get -u github.com/lib/pq       # upgrade to latest minor
go get -u=patch github.com/lib/pq # upgrade patches only
go get github.com/lib/[email protected]  # pin specific version

# View dependency graph
go mod graph | head

go test とベンチマーク

go test は _test.go ファイルを実行し、TestXxx(t *testing.T) 関数がテスト、BenchmarkXxx(b *testing.B) がベンチマークです。-race はデータ競合検出器を有効にします(並行コードに不可欠)。-cover はテストカバレッジを表示し、-coverprofile は詳細レポートを生成します。ベンチマークは b.N 反復を実行し、N は自動調整されます。テーブル駆動テスト(入力/期待値のスライス)が Go の慣用的なテストスタイルです。

go
# Run all tests in the current package
go test ./...

# Verbose output
go test -v ./...

# Run a specific test
go test -run TestAdd -v

# Run benchmarks
go test -bench=. -benchmem

# Race detector (find data races)
go test -race ./...

# Coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out  # view in browser

# --- test file example (xxx_test.go) ---
# func TestAdd(t *testing.T) {
#     got := Add(2, 3)
#     if got != 5 {
#         t.Errorf("Add(2,3) = %d, want 5", got)
#     }
# }
#
# func BenchmarkAdd(b *testing.B) {
#     for i := 0; i < b.N; i++ {
#         Add(2, 3)
#     }
# }

go fmt、go vet と golangci-lint

go fmt/gofmt は単一の標準フォーマットを強制します — Go にはフォーマット論争がありません。go vet は一般的なバグをキャッチします(printf の不一致、ロックのコピー、不正な構造体タグ)。golangci-lint は数十のリンターを集約し、CI の業界標準です。毎回のコミット前に gofmt と go vet を実行し、より深い分析のために golangci-lint を CI に追加してください。一貫したツールは Go コードベースが均一に見える主な理由です。

go
# go fmt: format code (the ONE true Go style)
go fmt ./...              # format all files
gofmt -d main.go          # show diff without changing
gofmt -w main.go          # write changes in place

# go vet: static analysis for common mistakes
go vet ./...              # check all packages
# Catches: printf format mismatches, unreachable code,
# struct tag errors, lock copies, shadowed variables

# golangci-lint: meta-linter (runs many linters)
# Install: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run         # run all enabled linters
golangci-lint run --enable=gosec,goconst  # enable specific linters

# Common linters:
# errcheck    - check unchecked errors
# gosec       - security issues
# govet       - go vet checks
# staticcheck - advanced static analysis
# ineffassign - detect ineffectual assignments
# unused      - find unused code

# goimports: fmt + auto-manage imports
# go install golang.org/x/tools/cmd/goimports@latest
goimports -w main.go

プロファイリングと pprof

pprof は Go の組み込みプロファイラーです — CPU、メモリ、ゴルーチン、ミューテックスのプロファイリング。長時間実行サーバーでは、import _ 'net/http/pprof' で /debug/pprof/ エンドポイントを公開し、再起動なしでライブプロファイリングが可能です。go tool pprof はインタラクティブシェル(top、list、web)または Web UI を提供します。実行トレーサー(go tool trace)はゴルーチンスケジューリングとブロッキングを可視化します。プロファイリングはパフォーマンスクリティカルな Go コードに不可欠です。

go
# CPU profiling
go test -cpuprofile=cpu.prof -bench=.
go tool pprof cpu.prof
# Interactive commands: top, list FunctionName, web (graphviz)

# Memory profiling
go test -memprofile=mem.prof -bench=.
go tool pprof mem.prof

# Built-in HTTP pprof endpoint (for production servers)
import _ "net/http/pprof"
# Then visit: http://localhost:8080/debug/pprof/
# Capture a 30-second CPU profile:
# go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

# In code:
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// ... code to profile ...

# Trace (execution tracer)
go test -trace=trace.out -bench=.
go tool trace trace.out  # opens in browser
20

データベース(SQL)

database/sql の基礎

database/sql は SQL データベースの標準インターフェースです。ドライバは副作用(自身の登録)のためにインポートされます。sql.Open は接続を確立しません — Ping で確認してください。常に defer db.Close() を使用してください。

go
import "database/sql"
import _ "github.com/lib/pq"
db, err := sql.Open("postgres", "host=localhost dbname=mydb")
if err != nil { log.Fatal(err) }
defer db.Close()
err = db.Ping()

行のクエリ

Query は複数行を返します。リソースを解放するため常に defer rows.Close() を使用してください。Scan は列の値を変数にコピーします。反復エラーのためにループ後に rows.Err() をチェックしてください。

go
rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 18)
if err != nil { log.Fatal(err) }
defer rows.Close()
for rows.Next() {
    var id int; var name string
    if err := rows.Scan(&id, &name); err != nil { log.Fatal(err) }
    fmt.Printf("%d: %s\n", id, name)
}

単一行のクエリ

QueryRow は単一行を返します。一致する行がない場合、Scan は sql.ErrNoRows を返します。単一行ルックアップでは Query よりクリーンです。常に ErrNoRows を明示的に処理してください。

go
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)
if err != nil {
    if err == sql.ErrNoRows { fmt.Println("Not found") } else { log.Fatal(err) }
}

プリペアドステートメント

Prepare は再利用可能なステートメントを作成し、繰り返しクエリのパフォーマンスを向上させます。SQL インジェクションを防ぎます。常に defer stmt.Close() を使用してください。複数回実行されるクエリに使用します。

go
stmt, err := db.Prepare("INSERT INTO users(name) VALUES($1)")
if err != nil { log.Fatal(err) }
defer stmt.Close()
_, err = stmt.Exec("Alice")
_, err = stmt.Exec("Bob")

トランザクション

Begin はトランザクションを開始します。tx 内の全操作はアトミックです。defer Rollback は安全です:Commit 後は no-op です。いずれかの操作が失敗した場合、Rollback が全変更を取り消します。

go
tx, err := db.Begin()
if err != nil { log.Fatal(err) }
defer tx.Rollback()  // Safe to call after commit
_, err = tx.Exec("UPDATE accounts SET bal = bal - 100 WHERE id = 1")
_, err = tx.Exec("UPDATE accounts SET bal = bal + 100 WHERE id = 2")
if err != nil { log.Fatal(err) }
err = tx.Commit()
21

テストの深掘り

基本テスト

テスト関数は Test で始まり *testing.T を取ります。t.Errorf は失敗を記録して継続し、t.Fatalf は記録して停止します。go test で実行します。よりクリーンなアサーションには testify/assert を使用してください。

go
func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5
    if got != want {
        t.Errorf("Add(2,3) = %d; want %d", got, want)
    }
}

テーブル駆動テスト

テーブル駆動テストは Go で慣用的です。テストケースを構造体のスライスとして定義し、ループで各々を実行します。t.Run は個別の失敗用にサブテストを作成します。

go
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {1, 2, 3}, {-1, 1, 0}, {0, 0, 0},
    }
    for _, tt := range tests {
        got := Add(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Add(%d,%d)=%d; want %d", tt.a, tt.b, got, tt.want)
        }
    }
}

サブテスト

サブテストは名前と関数を伴う t.Run を使用します。-run パターンで特定のサブテストを実行します。より良いテスト編成と出力を提供します。

go
func TestAdd(t *testing.T) {
    t.Run("positive", func(t *testing.T) {
        if Add(1, 2) != 3 { t.Error("failed") }
    })
    t.Run("negative", func(t *testing.T) {
        if Add(-1, -2) != -3 { t.Error("failed") }
    })
}
// Run specific: go test -run TestAdd/negative

Test Main

TestMain はパッケージに対して1回実行され、デフォルトのテストランナーを置き換えます。グローバルセットアップ/ティアダウンに使用します。テストを実行するには m.Run() を呼び出す必要があります。os.Exit が終了コードを伝播します。

go
func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    teardown()
    os.Exit(code)
}
func setup() { /* initialize DB, etc. */ }
func teardown() { /* cleanup */ }

インターフェースのモック

Go のモックはインターフェースに依存します。インターフェースを定義し、モックを実装し、注入します。mockery や mockgen のようなツールがモックを自動生成します。外部依存関係のないユニットテストを可能にします。

go
type Store interface { Get(id int) (string, error) }
type MockStore struct { data map[int]string }
func (m *MockStore) Get(id int) (string, error) {
    if v, ok := m.data[id]; ok { return v, nil }
    return "", errors.New("not found")
}
// Use in tests by injecting MockStore
22

ベンチマーク

基本ベンチマーク

ベンチマーク関数は Benchmark で始まり *testing.B を取ります。b.N は安定した測定を得るためにフレームワークが調整します。go test -bench で実行します。出力は ns/op(操作あたりのナノ秒)を表示します。

go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
// Run: go test -bench=.
// Output: BenchmarkAdd-8    1000000000    0.3 ns/op

サブベンチマーク

サブベンチマークは b.Run を使用します。ResetTimer はセットアップ時間を除外します。ReportAllocs はメモリアロケーションを表示します。入力サイズ間でパフォーマンスを比較します。

go
func BenchmarkSort(b *testing.B) {
    sizes := []int{100, 1000, 10000}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
            data := generateData(size)
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                Sort(data)
            }
        })
    }
}

メモリアロケーション

ReportAllocs は操作あたりのメモリアロケーションを表示します。アロケーションの削減が Go パフォーマンスの鍵です。sync.Pool を使用し、スライスを事前割り当てし、不要な文字列連結を避けてください。

go
func BenchmarkConcat(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        s := "a" + "b" + "c"
        _ = s
    }
}
// Output includes allocs/op and B/op

並列ベンチマーク

RunParallel は複数のゴルーチンにまたがってベンチマークを並行実行します。スレッドセーフなコードのテストに有用です。pb.Next() が作業分布を反復します。並行処理下のスループットを測定します。

go
func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(2, 3)
        }
    })
}

ベンチマーク比較

benchstat はベンチマーク結果を統計的に比較します。信頼性のある比較のため -count=10 で複数回実行してください。パフォーマンス改善の検証とリグレッションの検出に役立ちます。

go
# Run with memory stats
benchstat old.txt new.txt
# Compare two runs
# name      old time/op  new time/op  delta
# Add-8     2.3ns        1.8ns        -21.7%
23

プロファイリング(pprof)

CPU プロファイリング

StartCPUProfile は CPU プロファイルをファイルに書き込みます。StopCPUProfile がデータをフラッシュします。go tool pprof で分析します。pprof で top、list、web コマンドを使用し、最も CPU を消費する関数に注目します。

go
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Run your code here
// Analyze: go tool pprof cpu.prof

メモリプロファイリング

WriteHeapProfile は現在のヒープ状態をキャプチャします。正確な結果のため先に runtime.GC() を呼び出してください。MemProfileRate = 1 で全アロケーションをプロファイルします(遅いが正確)。

go
// Add to code
runtime.GC()
f, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(f)
f.Close()
// Or use runtime.MemProfileRate = 1 for all allocations

HTTP プロファイリング

net/http/pprof はデフォルトの mux にプロファイリングエンドポイントを登録します。再起動なしで HTTP 経由でプロファイルにアクセスできます。本番診断に有用です。本番ではエンドポイントを保護してください。

go
import _ "net/http/pprof"
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Analyze live:
// go tool pprof http://localhost:6060/debug/pprof/profile

pprof コマンド

top は最もリソースを消費する関数を表示します。list は注釈付きソースコードを表示します。web は SVG コールグラフを開きます。tree はコール階層を表示します。focus でフィルタします。

go
# Start interactive pprof
go tool pprof cpu.prof
(pprof) top 10        # Top functions by CPU
(pprof) list Add      # Show source with timings
(pprof) web           # Open graph in browser
(pprof) tree          # Call tree
(pprof) png > out.png # Save graph as PNG

トレース

runtime/trace は実行トレースをキャプチャします:ゴルーチンスケジューリング、GC、システムコールブロッキング。go tool trace が Web UI を開きます。レイテンシと並行性の問題の診断に有用です。

go
import "runtime/trace"
f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()
// Run code
// View: go tool trace trace.out
24

ビルドタグ

基本ビルドタグ

ビルドタグはファイルを条件付きでコンパイルします。//go:build 構文(Go 1.17+)が // +build を置き換えます。タグは OS(linux、darwin、windows)、アーキテクチャ(amd64、arm64)、またはカスタムです。

go
//go:build linux
// +build linux

package main
// This file only compiles on Linux

複数タグ

&&(旧構文のカンマ)は全タグが必要。||(旧構文のスペース)はいずれかのタグが必要。! は否定です。プラットフォーム固有の実装に使用します。

go
//go:build linux && amd64
// +build linux,amd64

//go:build linux || darwin
// +build linux darwin

//go:build !windows
// +build !windows

カスタムタグ

カスタムタグはオプション機能を有効にします。-tags フラグでビルドします。一般的な用途:デバッグビルド、実験的機能、エンタープライズ版 vs コミュニティ版。タグ名は小文字にしてください。

go
//go:build debug
// +build debug

package main
const DebugMode = true
// Build with: go build -tags debug

ファイルサフィックス

ファイルサフィックスは暗黙のビルド制約を提供します。OS には _linux、_darwin、_windows。アーキテクチャには _amd64、_arm64。プラットフォーム固有コードにはタグよりシンプルです。

go
// file: utils_linux.go    -> Linux only
// file: utils_darwin.go   -> macOS only
// file: utils_windows.go  -> Windows only
// file: utils_amd64.go    -> amd64 only
// file: utils_debug.go    -> requires -tags debug

タグ付きビルド

-tags で条件付きコンパイルを有効にします。複数タグはスペース区切りです。Go バータグ(go1.18)でバージョン固有コードを有効にします。go list -tags で確認します。

go
# Build with debug tag
go build -tags debug
# Multiple tags
go build -tags "debug verbose"
# List available tags
go list -tags ./...
# Conditional in code
//go:build go1.18  // Requires Go 1.18+
25

CGO の基礎

基本 CGO

CGO は Go から C を呼び出すことを可能にします。C コードは import "C" の上のコメント内にあります。インポートはコメントの直後でなければなりません。CGO はビルドを遅くし、クロスコンパイルを妨げます。

go
/*
#include <stdio.h>
void hello() {
    printf("Hello from C!\n");
}
*/
import "C"
func main() {
    C.hello()
}

文字列の受け渡し

C.CString は C 文字列をアロケートします(C.free で解放必須)。CString はデータをコピーします。変換には unsafe.Pointer を使用します。リークを避けるため C アロケートメモリは常に解放してください。

go
/*
#include <string.h>
int len(const char* s) { return strlen(s); }
*/
import "C"
import "unsafe"
s := "Hello"cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
length := C.len(cs)

C ライブラリの呼び出し

#cgo LDFLAGS が C ライブラリをリンクします。#cgo CFLAGS がコンパイラフラグを設定します。#include でシステムヘッダをインクルードします。CGO は Go と libm、libcrypto のような既存 C ライブラリを橋渡しします。

go
/*
#cgo LDFLAGS: -lm
#include <math.h>
*/
import "C"
result := float64(C.sqrt(16.0))
fmt.Println(result)  // 4

C から Go へのコールバック

//export でエクスポートされた Go 関数は C から呼び出せます。関数は package main になければなりません。C ライブラリが Go にコールバックすることを可能にします。FFI バインディングで使用されます。

go
/*
extern void goCallback(int);
void callGo(int n) { goCallback(n); }
*/
import "C"
//export goCallback
func goCallback(n C.int) {
    fmt.Printf("Called with %d\n", n)
}

パフォーマンスの注意

CGO 呼び出しは Go 関数呼び出しに比べ大幅なオーバーヘッドがあります。パフォーマンスクリティカルなコードでは避けてください。クロス境界呼び出しを減らすため操作をバッチ化します。純 Go ビルドには CGO_ENABLED=0 を設定します。

go
// CGO calls have overhead (~100ns)
// Avoid in hot paths
// Batch C calls to amortize cost
// CGO disables inlining and some optimizations
// Build: CGO_ENABLED=1 go build
26

Web フレームワーク

net/http サーバー

net/http は標準ライブラリの HTTP サーバーです。HandleFunc がハンドラを登録し、ListenAndServe がサーバーを開始します。シンプルなアプリにはデフォルトの mux で十分です。本番にはカスタム mux を使用してください。

go
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
})
http.HandleFunc("/api", apiHandler)
log.Fatal(http.ListenAndServe(":8080", nil))

Gin フレームワーク

Gin は高性能 HTTP フレームワークです。Param が URL パラメータを抽出し、JSON がレスポンスをシリアライズします。Gin はルーティング、ミドルウェア、JSON バリデーションを提供します。httprouter により net/http より高速です。

go
import "github.com/gin-gonic/gin"
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"user": id})
})
r.Run(":8080")

ミドルウェア

ミドルウェアはハンドラをラップして横断的関心事を追加します:ロギング、認証、CORS。c.Next() が次のハンドラを呼び出します。gin.Recovery() がパニックによるクラッシュを防ぎます。順序が重要です。

go
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        fmt.Printf("%s %s %v\n", c.Request.Method, c.URL.Path, time.Since(start))
    }
}
r := gin.New()
r.Use(Logger(), gin.Recovery())

Echo フレームワーク

Echo は Gin に似たもう一つの人気フレームワークです。ハンドラは集中型エラーハンドリングのためにエラーを返します。CORS、JWT、レートリミットの組み込みミドルウェア。クリーンな API デザイン。

go
import "github.com/labstack/echo/v4"
e := echo.New()
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(200, map[string]string{"user": id})
})
e.Start(":8080")

静的ファイル

FileServer が静的ファイルを配信します。StripPrefix が URL パスを調整します。HTML、CSS、JS、画像の配信に有用です。本番では静的アセットに CDN や nginx を使用してください。

go
// net/http
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Gin
r.Static("/assets", "./assets")
r.StaticFile("/favicon.ico", "./favicon.ico")
27

gRPC

Proto の定義

Protocol Buffers がサービス契約を定義します。proto3 が最新の構文です。service が RPC メソッドを定義し、message がデータ構造を定義します。protoc で Go コードを生成します。

go
syntax = "proto3";
package greet;
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }

コード生成

protoc が .proto ファイルから Go コードを生成します。--go_out がメッセージ型を生成し、--go-grpc_out がサービススタブを生成します。生成されたコードは手動編集しません。

go
# Install protoc and plugins
protoc --go_out=. --go-grpc_out=. \
    greet.proto
# This generates greet.pb.go and greet_grpc.pb.go
# Containing message types and service interfaces

サーバー実装

前方互換性のため UnimplementedGreeterServer を埋め込みます。サービスメソッドを実装します。grpc.NewServer がサーバーを作成します。サービス前に登録します。

go
type server struct { greet.UnimplementedGreeterServer }
func (s *server) SayHello(ctx context.Context, in *greet.HelloRequest) (*greet.HelloReply, error) {
    return &greet.HelloReply{Message: "Hello " + in.Name}, nil
}
lis, _ := net.Listen("tcp", ":50051")
grpc.NewServer().Serve(lis)

クライアント

grpc.Dial が接続を確立します。WithInsecure は TLS を無効にします(本番では credentials.NewTLS を使用)。クライアントスタブが型付きメソッドを提供します。接続はプールされ再利用されます。

go
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := greet.NewGreeterClient(conn)
resp, _ := client.SayHello(context.Background(), &greet.HelloRequest{Name: "Alice"})
fmt.Println(resp.Message)

ストリーミング

gRPC は3つのストリーミングパターンをサポートします。stream キーワードがストリーミングをマークします。サーバーストリーミング:1リクエスト、複数レスポンス。双方向:両側がストリーミング。リアルタイムデータに有用です。

go
// Server streaming
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply);
// Client streaming
rpc LotsOfGreetings(stream HelloRequest) returns (HelloReply);
// Bidirectional
rpc BidiHello(stream HelloRequest) returns (stream HelloReply);
28

エラーラッピング

エラーのラッピング

%w 動詞でエラーをラップし、元のエラーを保持します。これによりエラーチェーンが作成されます。ラッピングに %v を使用するとチェーンが失われるため避けてください。ラッピングは根本原因を失わずにコンテキストを追加します。

go
if err != nil {
    return fmt.Errorf("failed to open config: %w", err)
}

アンラッピング

errors.Is はチェーン内のいずれかのエラーが一致するかチェックします。errors.As はチェーンから特定のエラー型を抽出します。センチネル値には Is を、型付きエラーには As を使用してください。両方ともラップチェーンを走査します。

go
err := someOperation()
if errors.Is(err, sql.ErrNoRows) {
    // Handle not found
}
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Path:", pathErr.Path)
}

カスタムエラー

カスタムエラー型は error インターフェースを実装します。エラーハンドリング用の構造化データを保持します。errors.As でカスタム型を抽出します。文字列比較より型付きエラーを優先してください。

go
type ValidationError struct {
    Field string
    Msg   string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
func validate(s string) error {
    if s == "" { return &ValidationError{Field: "name", Msg: "required"} }
    return nil
}

センチネルエラー

センチネルエラーはパッケージレベルのエラー変数です。予期されるエラー条件に使用します。== ではなく errors.Is でチェックしてください。ユーザーがチェックできるようエクスポートします。

go
var ErrNotFound = errors.New("not found")
func Find(id int) (*User, error) {
    if id > 100 { return nil, ErrNotFound }
    return &User{}, nil
}
// Check with errors.Is
if errors.Is(err, ErrNotFound) { /* ... */ }

エラーハンドリングパターン

可能な場合はエラーを即座に処理します。Close のような遅延操作ではエラーをキャプチャします。名前付き戻り値で遅延関数が戻り値を変更できます。Close エラーは常にチェックしてください。

go
// Immediate handling
if err := doSomething(); err != nil {
    return fmt.Errorf("operation failed: %w", err)
}
// Deferred error checking (e.g., Close)
func readFile() (err error) {
    f, e := os.Open("file.txt")
    if e != nil { return e }
    defer func() {
        if cerr := f.Close(); err == nil { err = cerr }
    }()
    // ...
}
29

よくある落とし穴

ゴルーチンリーク

ゴルーチンは永遠にブロックするとリークします。常に終了パスを提供してください:context キャンセル、チャネルのクローズ、またはバッファ付きチャネル。runtime.NumGoroutine() でリークを検出します。

go
// BUG: goroutine leaks if receiver stops
func send(ch chan int) {
    go func() { ch <- 1 }()  // Blocks forever
}
// FIX: use context or buffered channel
func send(ctx context.Context, ch chan int) {
    go func() {
        select {
        case ch <- 1:
        case <-ctx.Done():
        }
    }()
}

チャネルのクローズ

チャネルをクローズすべきなのは送信側のみで、受信側ではありません。クローズは値がこれ以上ないことを通知します。クローズされたチャネルからの受信はゼロ値を返します。クローズされたチャネルへの送信はパニックします。

go
// Only the sender should close a channel
// Closing from receiver causes panic
ch := make(chan int)
go func() {
    defer close(ch)  // Sender closes
    for i := 0; i < 5; i++ { ch <- i }
}()
for v := range ch { fmt.Println(v) }

ループ変数のキャプチャ

Go 1.22 以前では、ループ変数は反復間で共有されていました。キャプチャするゴルーチンは最終値を見ます。Go 1.22+ は反復ごとに新しい変数を作成して修正します。旧バージョンではパラメータとして渡してください。

go
// BUG (Go < 1.22): all goroutines see last value
for i := 0; i < 3; i++ {
    go func() { fmt.Println(i) }()  // Prints 3,3,3
}
// FIX: pass as parameter
for i := 0; i < 3; i++ {
    go func(i int) { fmt.Println(i) }(i)
}

マップの並行性

マップは並行使用に対して安全ではありません。並行な読み取りと書き込みはランタイムパニックを引き起こします。明示的ロックには sync.Mutex を、読み取り中心の並行アクセスには sync.Map を使用してください。

go
// BUG: concurrent map writes panic
m := map[int]int{}
go func() { m[1] = 1 }()
go func() { m[2] = 2 }()
// FIX: use sync.Map or mutex
var mu sync.Mutex
mu.Lock(); m[1] = 1; mu.Unlock()

nil インターフェース

インターフェースにラップされた nil ポインタは nil ではありません。値が nil でもインターフェースは型を持ちます。nil 型付きポインタではなく、常に nil を直接返してください。reflect でチェックするか明示的に nil を返します。

go
// BUG: nil check fails
type MyError struct{}
func (e *MyError) Error() string { return "err" }
func doSomething() error {
    var err *MyError = nil
    return err  // Non-nil interface!
}
// FIX: return nil explicitly
func doSomething() error {
    return nil
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.