第6章: 関数とメソッド

学習目標

この章を終えると、以下ができるようになります:

  • 関数を定義し、複数の値を返せる
  • 名前付き戻り値と裸のreturnを使える
  • メソッドとレシーバを理解できる
  • 関数型プログラミングの基本を使える
  • 関数呼び出しのスタック動作を理解できる
  • クロージャとdeferの内部実装を理解できる

1. 関数の基本

1.1 関数定義

// 基本的な関数
func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

// 戻り値を持つ関数
func add(a, b int) int {
    return a + b
}

// 同じ型の引数は省略可能
func multiply(a, b, c int) int {
    return a * b * c
}

// 異なる型の引数
func describe(name string, age int, active bool) {
    fmt.Printf("%s is %d years old, active: %t\n", name, age, active)
}

1.2 複数戻り値

Goの最も特徴的な機能の一つです。エラーハンドリングでよく使われます。

// 複数の値を返す
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// 使用例
func main() {
    result, err := divide(10, 2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Result:", result)
}

// 3つ以上の値も可能
func getUserInfo(id int) (string, int, string, error) {
    // データベースから取得
    return "Alice", 25, "alice@example.com", nil
}

戻り値の無視:

// 不要な戻り値は _ で無視
result, _ := divide(10, 2)  // エラーを無視

// すべて無視
_, _, _, _ = getUserInfo(1)

1.3 名前付き戻り値

戻り値に名前を付けることができます。

// 名前付き戻り値
func calculate(x, y int) (sum, product int) {
    sum = x + y
    product = x * y
    return  // 裸のreturn(名前付き戻り値を返す)
}

// 明示的に返すこともできる
func calculateExplicit(x, y int) (sum, product int) {
    sum = x + y
    product = x * y
    return sum, product  // 明示的return
}

実用例: エラーハンドリング:

func readFile(filename string) (content []byte, err error) {
    file, err := os.Open(filename)
    if err != nil {
        return  // nil, errを返す
    }
    defer file.Close()

    content, err = io.ReadAll(file)
    if err != nil {
        return  // nil, errを返す
    }

    return  // content, nilを返す
}

💡 注意点:

// 裸のreturnは短い関数でのみ使う
func bad() (result int, err error) {
    // ... 100行のコード ...
    if something {
        result = 42
    } else if other {
        result = 99
    }
    // ... さらに100行 ...
    return  // resultの値がわかりにくい!
}

// 長い関数では明示的returnを使う
func better() (int, error) {
    // ... コード ...
    return 42, nil  // 明確
}

1.4 可変長引数

// 可変長引数
func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

// 使用例
fmt.Println(sum(1, 2, 3))        // 6
fmt.Println(sum(1, 2, 3, 4, 5))  // 15
fmt.Println(sum())               // 0

// スライスを展開して渡す
nums := []int{1, 2, 3, 4}
fmt.Println(sum(nums...))  // 10

通常引数と組み合わせ:

// 可変長引数は最後のパラメータのみ
func format(prefix string, values ...interface{}) string {
    var result string
    result += prefix + ": "
    for i, v := range values {
        if i > 0 {
            result += ", "
        }
        result += fmt.Sprintf("%v", v)
    }
    return result
}

fmt.Println(format("Values", 1, 2, 3))           // Values: 1, 2, 3
fmt.Println(format("Mixed", "a", 42, true))      // Mixed: a, 42, true

2. 関数呼び出しのスタック動作

2.1 CALL/RET命令の仕組み

🔑 重要: 関数呼び出しは、CPUのCALL命令とスタック操作によって実現されます。

CALL命令の動作

CALL命令が実行されるとき:

1. 現在のRIP(命令ポインタ)の次の命令アドレスをスタックにPUSH
2. RIPに関数の先頭アドレスをセット
3. 関数本体へジャンプ

メモリレイアウト:

高位アドレス
┌──────────────┐
│   引数3      │
├──────────────┤
│   引数2      │
├──────────────┤
│   引数1      │
├──────────────┤
│ 戻りアドレス │ ← CALL命令がPUSH
├──────────────┤
│   ローカル1  │
├──────────────┤
│   ローカル2  │
├──────────────┤ ← SP (Stack Pointer)
低位アドレス

RET命令の動作

RET命令が実行されるとき:

1. スタックから戻りアドレスをPOP
2. RIPにその戻りアドレスをセット
3. 呼び出し元の次の命令へジャンプ

具体例:

func main() {
    x := add(3, 5)  // ①CALL命令
    println(x)      // ④ここに戻る
}

func add(a, b int) int {
    sum := a + b    // ②実行
    return sum      // ③RET命令
}

実行フロー:

main関数内:
  mov  rdi, 3        ; 第1引数
  mov  rsi, 5        ; 第2引数
  call add           ; ①戻りアドレスをPUSH & ジャンプ

add関数内:
  ; プロローグ
  push rbp           ; ②ベースポインタを保存
  mov  rbp, rsp      ; 新しいフレーム設定

  ; 関数本体
  mov  rax, rdi      ; a を rax へ
  add  rax, rsi      ; rax += b

  ; エピローグ
  pop  rbp           ; ③ベースポインタを復元
  ret                ; 戻りアドレスをPOP & ジャンプ

main関数へ戻る:
  mov  QWORD [x], rax  ; ④戻り値を保存

2.2 スタックフレーム構造

🔑 スタックフレーム: 各関数呼び出しごとに作られるメモリ領域

完全なスタックフレーム構造:

高位アドレス
┌─────────────────┐
│  呼び出し元の   │
│  スタックフレーム │
├─────────────────┤
│   引数 N        │
│   ...           │
│   引数 2        │
│   引数 1        │  ← 引数エリア
├─────────────────┤
│  戻りアドレス    │  ← CALL がPUSH
├─────────────────┤
│  保存された RBP  │  ← 関数プロローグがPUSH
├─────────────────┤ ← RBP (Base Pointer)
│  ローカル変数1   │
│  ローカル変数2   │
│  ...            │
│  ローカル変数N   │  ← ローカル変数エリア
├─────────────────┤
│  一時変数        │  ← 一時領域
├─────────────────┤ ← RSP (Stack Pointer)
│  次の関数の引数   │
低位アドレス

アクセス方法:

引数アクセス:    [RBP + 16], [RBP + 24], ...
ローカル変数:    [RBP - 8], [RBP - 16], ...

実例: 複雑な関数呼び出し

func calculate(a, b, c int) (sum, product int) {
    sum = a + b + c
    product = a * b * c
    temp := sum + product  // ローカル変数
    return sum, product
}

func main() {
    s, p := calculate(2, 3, 4)
    fmt.Println(s, p)
}

スタックフレームの変化:

main開始:
┌──────────────┐
│  main frame  │ ← RSP, RBP
└──────────────┘

calculate呼び出し前(引数セット):
┌──────────────┐
│  main frame  │ ← RBP
├──────────────┤
│  a = 2       │
│  b = 3       │
│  c = 4       │ ← 引数をレジスタかスタックへ
└──────────────┘ ← RSP

CALL calculate後:
┌──────────────┐
│  main frame  │
├──────────────┤
│  a = 2       │
│  b = 3       │
│  c = 4       │
├──────────────┤
│  戻りアドレス │
├──────────────┤
│  保存RBP     │ ← 新しいRBP
├──────────────┤
│  sum = 0     │
│  product = 0 │
│  temp = 0    │ ← ローカル変数
└──────────────┘ ← RSP

RET前(戻り値セット):
  sum = 9, product = 24 を戻り値レジスタへ

RET後:
┌──────────────┐
│  main frame  │ ← RSP, RBP
├──────────────┤
│  s = 9       │
│  p = 24      │
└──────────────┘

2.3 引数と戻り値の配置

従来のスタック渡し(古いGo)

引数はすべてスタック上:

func example(a, b, c int, s string) int

スタック:
高位アドレス
┌──────────────┐
│  s (string)  │
│   - ptr      │
│   - len      │
├──────────────┤
│  c (int)     │
├──────────────┤
│  b (int)     │
├──────────────┤
│  a (int)     │
├──────────────┤
│  戻りアドレス │
低位アドレス

Go 1.17以降のレジスタ渡し

🔑 重要: Go 1.17からレジスタベースの呼び出し規約に変更されました。

引数の優先順位:
1. 整数・ポインタ: RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11
2. 浮動小数点: XMM0-XMM14
3. レジスタに収まらない場合のみスタック

戻り値の優先順位:
1. 第1戻り値: RAX
2. 第2戻り値: RBX
3. 第3戻り値: RCX
4. それ以降: スタック

例: 2つの整数を引数に取る関数

func add(a, b int) int {
    return a + b
}

; Go 1.17以降
add:
    mov  rax, rdi      ; a はRDIレジスタから
    add  rax, rsi      ; b はRSIレジスタから
    ret                ; 結果はRAXレジスタへ

; Go 1.16以前
add:
    mov  rax, [rsp+8]  ; a はスタックから
    mov  rbx, [rsp+16] ; b はスタックから
    add  rax, rbx
    mov  [rsp+24], rax ; 結果をスタックへ
    ret

例: 複数戻り値

func divmod(a, b int) (quotient, remainder int) {
    return a / b, a % b
}

divmod:
    mov  rax, rdi      ; a を RAX へ
    xor  rdx, rdx      ; RDX をクリア
    div  rsi           ; RAX / RSI → RAX(商), RDX(余)
    mov  rbx, rdx      ; 余りを RBX へ
    ret                ; RAX=商, RBX=余り

💡 パフォーマンス改善:

レジスタ渡しの利点:
1. メモリアクセスが不要(L1キャッシュより速い)
2. スタック操作のオーバーヘッド削減
3. 小さな関数で20-30%の高速化

2.4 ネストした関数呼び出し

func main() {
    result := funcA(10)
    println(result)
}

func funcA(x int) int {
    return funcB(x) + 5
}

func funcB(x int) int {
    return funcC(x) * 2
}

func funcC(x int) int {
    return x + 3
}

スタックの成長過程:

[1] main開始
┌──────────────┐
│  main frame  │
└──────────────┘

[2] funcA呼び出し
┌──────────────┐
│  main frame  │
├──────────────┤
│ funcA frame  │
│  x = 10      │
└──────────────┘

[3] funcB呼び出し
┌──────────────┐
│  main frame  │
├──────────────┤
│ funcA frame  │
├──────────────┤
│ funcB frame  │
│  x = 10      │
└──────────────┘

[4] funcC呼び出し(最深部)
┌──────────────┐
│  main frame  │
├──────────────┤
│ funcA frame  │
├──────────────┤
│ funcB frame  │
├──────────────┤
│ funcC frame  │
│  x = 10      │
└──────────────┘

[5] funcC完了 (戻り値: 13)
┌──────────────┐
│  main frame  │
├──────────────┤
│ funcA frame  │
├──────────────┤
│ funcB frame  │
│  RAX = 13    │
└──────────────┘

[6] funcB完了 (戻り値: 26)
┌──────────────┐
│  main frame  │
├──────────────┤
│ funcA frame  │
│  RAX = 26    │
└──────────────┘

[7] funcA完了 (戻り値: 31)
┌──────────────┐
│  main frame  │
│  RAX = 31    │
└──────────────┘

⚠️ スタックオーバーフロー:

// 無限再帰
func infinite(n int) {
    println(n)
    infinite(n + 1)  // スタックが枯渇!
}

// Goのスタックサイズ: 2KB(初期)→ 1GB(最大)
// スタック拡張で数千回の再帰は可能
// しかし無限再帰は最終的にクラッシュ

3. Go ABIと呼び出し規約

3.1 Go ABIの特徴

🔑 ABI (Application Binary Interface): バイナリレベルでの関数呼び出しルール

Go 1.17以降の主な特徴:

1. レジスタベース
   - 引数: 整数9個、浮動小数点15個までレジスタ
   - 戻り値: 最初の数個はレジスタ

2. スタック拡張
   - 動的にスタックを拡張
   - 初期2KB → 必要に応じて拡張

3. ゼロレジスタなし
   - x86-64にゼロレジスタはないが、XOR で実現

4. 呼び出し先保存(Callee-saved)
   - RBX, RBP, R12-R15は関数が保存
   - その他は呼び出し元保存

5. GCのためのメタデータ
   - スタックマップ
   - ポインタビットマップ

3.2 レジスタ使用規則

レジスタ割り当て(x86-64):

整数/ポインタ引数:
  RAX: 第1引数 または 第1戻り値
  RBX: 第2引数 または 第2戻り値  (callee-saved)
  RCX: 第3引数 または 第3戻り値
  RDI: 第4引数
  RSI: 第5引数
  R8:  第6引数
  R9:  第7引数
  R10: 第8引数
  R11: 第9引数

浮動小数点引数:
  XMM0-XMM14: 浮動小数点引数・戻り値

特殊用途:
  RSP: スタックポインタ
  RBP: ベースポインタ(フレームポインタ)
  R12-R15: callee-saved(関数内で自由に使える)
  R14: 現在のGoroutine情報へのポインタ

一時レジスタ(caller-saved):
  RAX, RCX, RDX, RSI, RDI, R8-R11
  関数呼び出し時に破壊される可能性

実例:

func complex(a, b, c, d, e, f, g, h, i int) (int, int, int) {
    return a+b, c+d, e+f+g+h+i
}

complex:
    ; 引数レジスタ割り当て:
    ; RAX=a, RBX=b, RCX=c, RDI=d, RSI=e
    ; R8=f, R9=g, R10=h, R11=i

    ; 第1戻り値: a + b
    add  rax, rbx       ; RAX = a + b

    ; 第2戻り値: c + d
    add  rcx, rdi       ; RCX = c + d
    mov  rbx, rcx       ; RBX = 第2戻り値

    ; 第3戻り値: e + f + g + h + i
    add  rsi, r8
    add  rsi, r9
    add  rsi, r10
    add  rsi, r11
    mov  rcx, rsi       ; RCX = 第3戻り値

    ret                 ; RAX, RBX, RCX が戻り値

3.3 スタック拡張メカニズム

💡 Goの特徴: スタックは必要に応じて自動拡張されます。

スタック拡張の仕組み:

[1] 各関数のプロローグでスタックチェック
┌──────────────────┐
│ func example() { │
│   // チェック:    │
│   if RSP - size  │
│      < stackguard│
│   then 拡張       │
│ }                │
└──────────────────┘

[2] 拡張が必要な場合
    - 新しい大きなスタック領域を割り当て
    - 既存データをコピー
    - 古いスタックを破棄
    - スタックポインタを更新

スタックサイズ:
  初期: 2KB
  拡張: 2倍ずつ増加(4KB → 8KB → 16KB ...)
  最大: 1GB(64bit)、250MB(32bit)

実装例(疑似コード):

example:
    ; プロローグ
    lea   r12, [rsp - 128]      ; 必要なスタックサイズ
    cmp   r12, [r14 + stackguard]  ; R14 = Goroutine構造体
    jb    morestack              ; 足りなければ拡張

morestack:
    call  runtime.morestack_noctxt
    jmp   example                ; 拡張後に再実行

⚠️ 注意点:

// スタック拡張のコスト
func recursive(n int) {
    if n == 0 {
        return
    }
    var buffer [1000]byte  // 1KBのローカル変数
    recursive(n - 1)
}

// 2-3回の再帰でスタック拡張が発生
// 各拡張で:
//   1. 新領域の割り当て: メモリアロケーション
//   2. データコピー: O(stack_size)
//   3. GCのスタックマップ更新

3.4 C言語との比較

Go ABI vs C ABI (System V x86-64):

引数渡し:
  C:   RDI, RSI, RDX, RCX, R8, R9, 以降スタック
  Go:  RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11

戻り値:
  C:   RAXのみ(1個)
  Go:  RAX, RBX, RCX, ...(複数)

Callee-saved:
  C:   RBX, RBP, R12-R15
  Go:  RBX, RBP, R12-R15(同じ)

スタック:
  C:   固定サイズ
  Go:  動的拡張

特殊:
  C:   なし
  Go:  R14 = Goroutine構造体ポインタ

Cgoでの相互運用:

/*
#include <stdio.h>

int c_add(int a, int b) {
    return a + b;
}
*/
import "C"

func main() {
    // Go → C: ABI変換が必要
    result := C.c_add(3, 5)
    println(result)
}

Cgo呼び出しのオーバーヘッド:

Go関数呼び出し:
  - レジスタセット: 1-2 CPU命令
  - CALL/RET: 2 CPU命令
  → 合計 3-4 CPU命令

Cgo呼び出し:
  - Goランタイムの退出処理
  - Go ABI → C ABI変換
  - Cスタックへの切り替え
  - C関数実行
  - Goスタックへの復帰
  - C ABI → Go ABI変換
  - Goランタイムの再入処理
  → 合計 数百 CPU命令

パフォーマンス:
  純Go:  1ns
  Cgo:   100-200ns(100-200倍遅い)

4. 高階関数とクロージャ

4.1 関数を引数として受け取る

// 関数型
type Operation func(int, int) int

// 高階関数
func apply(a, b int, op Operation) int {
    return op(a, b)
}

// 使用例
func main() {
    add := func(x, y int) int { return x + y }
    multiply := func(x, y int) int { return x * y }

    fmt.Println(apply(3, 4, add))       // 7
    fmt.Println(apply(3, 4, multiply))  // 12
}

実用例: フィルタリング:

// Predicate型
type Predicate func(int) bool

// Filter関数
func Filter(numbers []int, predicate Predicate) []int {
    result := make([]int, 0)
    for _, n := range numbers {
        if predicate(n) {
            result = append(result, n)
        }
    }
    return result
}

// 使用例
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// 偶数のみ
evens := Filter(numbers, func(n int) bool {
    return n%2 == 0
})
fmt.Println(evens)  // [2, 4, 6, 8, 10]

// 5より大きい
greaterThan5 := Filter(numbers, func(n int) bool {
    return n > 5
})
fmt.Println(greaterThan5)  // [6, 7, 8, 9, 10]

4.2 関数を返す関数

// 関数を返す
func makeAdder(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

// 使用例
add5 := makeAdder(5)
add10 := makeAdder(10)

fmt.Println(add5(3))   // 8
fmt.Println(add10(3))  // 13

実用例: ミドルウェアパターン:

type Handler func(string) string

// ミドルウェア: ロギング
func withLogging(h Handler) Handler {
    return func(input string) string {
        log.Printf("Input: %s", input)
        result := h(input)
        log.Printf("Output: %s", result)
        return result
    }
}

// ミドルウェア: 時間計測
func withTiming(h Handler) Handler {
    return func(input string) string {
        start := time.Now()
        result := h(input)
        log.Printf("Took: %v", time.Since(start))
        return result
    }
}

// ベースハンドラ
func process(input string) string {
    return strings.ToUpper(input)
}

// 使用例
func main() {
    // ミドルウェアを重ねる
    handler := withLogging(withTiming(process))
    result := handler("hello")
    fmt.Println(result)  // HELLO
}

5. クロージャの実装

5.1 クロージャの基本

外側のスコープの変数にアクセスできる関数です。

// カウンターのクロージャ
func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// 使用例
counter1 := makeCounter()
counter2 := makeCounter()

fmt.Println(counter1())  // 1
fmt.Println(counter1())  // 2
fmt.Println(counter2())  // 1(別のカウンター)
fmt.Println(counter1())  // 3

5.2 キャプチャ変数のメモリ配置

🔑 重要: クロージャがキャプチャする変数は、ヒープに「エスケープ」します。

func makeCounter() func() int {
    count := 0  // この変数はヒープへエスケープ
    return func() int {
        count++
        return count
    }
}

メモリレイアウト:

makeCounter実行前:
スタック:
┌────────────────┐
│ makeCounter    │
└────────────────┘
ヒープ: (空)

makeCounter実行中(count定義後):
スタック:
┌────────────────┐
│ makeCounter    │
│  count = 0     │ ← 通常はここに配置されるが...
└────────────────┘

エスケープ分析後(コンパイラが検出):
  - count は返されるクロージャから参照される
  - makeCounter終了後もアクセスされる
  - → ヒープに移動!

実際のメモリ配置:
スタック:
┌────────────────┐
│ makeCounter    │
│  countPtr ───┐ │
└──────────────┼─┘
               │
ヒープ:        │
┌──────────────▼─┐
│  count = 0     │ ← ヒープ上に確保
└────────────────┘

makeCounter終了後:
スタック:
┌────────────────┐
│  main          │
│  counter1 ───┐ │
└──────────────┼─┘
               │
ヒープ:        │
┌──────────────▼─┐
│ クロージャ構造 │
│  funcPtr: ...  │
│  count: 0 ───┐ │
└──────────────┼─┘
               │
┌──────────────▼─┐
│  count = 0     │
└────────────────┘

5.3 関数値の内部構造

💡 関数値: Goでは関数は「ファーストクラス」の値です。

関数値の構造体(runtime.funcval):

type funcval struct {
    fn   uintptr        // 関数本体のアドレス
    // 以下、キャプチャした変数
}

通常の関数:
┌──────────────┐
│  funcval     │
│   fn: 0x...  │ → 関数コード
└──────────────┘

クロージャ:
┌──────────────┐
│  funcval     │
│   fn: 0x...  │ → クロージャラッパー関数
│   var1: ...  │ → キャプチャ変数1
│   var2: ...  │ → キャプチャ変数2
│   ...        │
└──────────────┘

実装詳細:

func makeAdder(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

add5 := makeAdder(5)

コンパイラが生成するコード(疑似コード):

// makeAdder の実装
func makeAdder(x int) func(int) int {
    // クロージャ構造体をヒープに割り当て
    closure := new(struct {
        fn uintptr
        x  int
    })
    closure.fn = addrof(closure_wrapper)
    closure.x = x

    return (*funcval)(closure)
}

// クロージャのラッパー関数
func closure_wrapper(closure *struct{fn uintptr; x int}, y int) int {
    return closure.x + y
}

// 呼び出し
add5(3)
// ↓ コンパイラが変換
closure_wrapper(add5, 3)

; makeAdder のアセンブリ(簡略化)
makeAdder:
    ; クロージャ構造体を割り当て(16バイト)
    mov  rdi, 16
    call runtime.newobject

    ; fn フィールドをセット
    lea  rbx, closure_wrapper
    mov  [rax], rbx

    ; x フィールドをセット(引数xをコピー)
    mov  [rax+8], rdi

    ; クロージャポインタを返す
    ret

; closure_wrapper
closure_wrapper:
    ; RAX = クロージャポインタ
    ; RBX = y引数
    mov  rcx, [rax+8]     ; x をロード
    add  rcx, rbx         ; x + y
    mov  rax, rcx         ; 結果を返す
    ret

5.4 複数変数のキャプチャ

func makeMultiplier(factor int) func(int) int {
    callCount := 0
    return func(x int) int {
        callCount++
        fmt.Printf("Call #%d\n", callCount)
        return x * factor
    }
}

mul3 := makeMultiplier(3)

メモリレイアウト:

ヒープ上のクロージャ構造:
┌────────────────────┐
│  funcval           │
│   fn: 0x...        │ → wrapper関数
├────────────────────┤
│ キャプチャ変数:    │
│   factor: 3        │
│   callCount: 0     │
└────────────────────┘

呼び出しごとに:
  mul3(10):
    - クロージャポインタをRAXへ
    - [RAX+16] (callCount) を読み込み
    - インクリメント
    - [RAX+16] に書き戻し
    - [RAX+8] (factor) を読み込み
    - x * factor を計算

5.5 クロージャのパフォーマンス

⚠️ コスト:

1. ヒープ割り当て:
   - 各クロージャ作成で malloc
   - GC の対象

2. 間接呼び出し:
   - 関数ポインタ経由の呼び出し
   - インライン化が困難

3. ポインタ参照:
   - キャプチャ変数へのアクセスは常にメモリ経由

ベンチマーク例:

// 通常の関数
func normalAdd(x, y int) int {
    return x + y
}

// クロージャ
func makeAdder(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}

func BenchmarkNormal(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = normalAdd(5, 3)
    }
}

func BenchmarkClosure(b *testing.B) {
    add := makeAdder(5)
    for i := 0; i < b.N; i++ {
        _ = add(3)
    }
}

結果(目安):
BenchmarkNormal-8    1000000000    0.25 ns/op
BenchmarkClosure-8    500000000    0.80 ns/op

クロージャは約3倍遅い(それでも十分速い)

原因:
  - 通常: 直接呼び出し、インライン化可能
  - クロージャ: 間接呼び出し、メモリアクセス

💡 最適化のヒント:

// ❌ ループ内で毎回クロージャ作成
for i := 0; i < 1000000; i++ {
    f := makeAdder(5)  // 100万回の割り当て
    result := f(i)
}

// ✅ ループ外で一度だけ作成
f := makeAdder(5)
for i := 0; i < 1000000; i++ {
    result := f(i)  // 割り当てなし
}

// ✅ クロージャを避ける
for i := 0; i < 1000000; i++ {
    result := i + 5  // 最も速い
}

5.6 実用例: イベントリスナー

type EventListener func(string)

type EventEmitter struct {
    listeners []EventListener
}

func (e *EventEmitter) On(listener EventListener) {
    e.listeners = append(e.listeners, listener)
}

func (e *EventEmitter) Emit(event string) {
    for _, listener := range e.listeners {
        listener(event)
    }
}

// 使用例
func main() {
    emitter := &EventEmitter{}

    // クロージャでリスナーを追加
    counter := 0
    emitter.On(func(event string) {
        counter++
        fmt.Printf("Event %d: %s\n", counter, event)
    })

    emitter.Emit("start")
    emitter.Emit("process")
    emitter.Emit("end")
}

6. defer の実装

6.1 defer の基本

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // 関数終了時に実行

    // ファイル処理
    data, err := io.ReadAll(file)
    if err != nil {
        return err  // この前に file.Close() が実行される
    }

    fmt.Println(string(data))
    return nil  // この前に file.Close() が実行される
}

6.2 defer チェインの実装

🔑 重要: defer は LIFO(後入れ先出し)のスタックで管理されます。

func example() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
    fmt.Println("Main")
}

// 出力:
// Main
// Third   ← 最後のdeferが最初に実行
// Second
// First   ← 最初のdeferが最後に実行

defer チェインの構造:

type _defer struct {
    siz     int32       // 引数サイズ
    started bool        // 実行開始フラグ
    sp      uintptr     // スタックポインタ
    pc      uintptr     // プログラムカウンタ
    fn      *funcval    // defer される関数
    _panic  *_panic     // 関連する panic
    link    *_defer     // 次の defer(リンクリスト)
}

Goroutine構造体:
┌──────────────────┐
│  Goroutine       │
│   _defer: ────┐  │
└───────────────┼──┘
                │
┌───────────────▼──┐
│  _defer #3       │
│   fn: println    │
│   arg: "Third"   │
│   link: ───┐     │
└────────────┼─────┘
             │
┌────────────▼─────┐
│  _defer #2       │
│   fn: println    │
│   arg: "Second"  │
│   link: ───┐     │
└────────────┼─────┘
             │
┌────────────▼─────┐
│  _defer #1       │
│   fn: println    │
│   arg: "First"   │
│   link: nil      │
└──────────────────┘

実行時の動作:

example() 実行:

[1] defer fmt.Println("First") に到達
    - _defer構造体を作成
    - Goroutineのdeferチェインに追加

[2] defer fmt.Println("Second") に到達
    - _defer構造体を作成
    - チェインの先頭に追加

[3] defer fmt.Println("Third") に到達
    - _defer構造体を作成
    - チェインの先頭に追加

[4] fmt.Println("Main") 実行
    出力: "Main"

[5] 関数終了前(RET命令前)
    - deferチェインを先頭から実行

    while g._defer != nil {
        d := g._defer
        g._defer = d.link
        d.fn()  // deferされた関数を呼び出し
    }

    実行順: Third → Second → First

6.3 defer のアセンブリ実装

func example() {
    defer println("deferred")
    println("normal")
}

example:
    ; プロローグ
    push  rbp
    mov   rbp, rsp
    sub   rsp, 32

    ; defer println("deferred") の登録
    mov   rdi, 16          ; _defer構造体のサイズ
    call  runtime.deferproc
    test  rax, rax
    jnz   .Ldefer_exit

    ; println("normal") の実行
    lea   rax, [.Lstr_normal]
    call  runtime.println

    ; 通常の終了
    call  runtime.deferreturn  ; deferを実行
    add   rsp, 32
    pop   rbp
    ret

.Ldefer_exit:
    ; deferproc がエラーを返した場合
    call  runtime.deferreturn
    add   rsp, 32
    pop   rbp
    ret

.Lstr_normal:   .ascii "normal"
.Lstr_deferred: .ascii "deferred"

6.4 defer のパフォーマンス影響

⚠️ コスト:

Go 1.13以前:
  - 各deferで _defer構造体をヒープ割り当て
  - チェイン管理のオーバーヘッド
  - コスト: 約50ns/defer

Go 1.13-1.21:
  - スタックベースの最適化(条件付き)
  - シンプルなdeferはスタック上に配置
  - コスト: 約10-20ns/defer

Go 1.22+:
  - さらなる最適化
  - インライン展開の改善
  - コスト: 約5-10ns/defer

ベンチマーク:

func withDefer() {
    defer func() {}()
    // 処理
}

func withoutDefer() {
    // 処理
}

func BenchmarkWithDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withDefer()
    }
}

func BenchmarkWithoutDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withoutDefer()
    }
}

結果(Go 1.22):
BenchmarkWithDefer-8       100000000    10.5 ns/op
BenchmarkWithoutDefer-8   1000000000     0.8 ns/op

defer のオーバーヘッド: 約10ns

実用上の影響:
  - ファイルI/O: 1-10ms → deferは無視できる
  - ネットワーク: 10-100ms → deferは無視できる
  - 超高速ループ: 1ns → deferは重要(避けるべき)

💡 最適化のヒント:

// ❌ ホットパス(高頻度実行)でのdefer
func process(data []int) int {
    mu.Lock()
    defer mu.Unlock()  // 毎回10nsのオーバーヘッド

    sum := 0
    for _, v := range data {
        sum += v
    }
    return sum
}

// ✅ 手動でUnlock(クリティカルな場合のみ)
func process(data []int) int {
    mu.Lock()
    sum := 0
    for _, v := range data {
        sum += v
    }
    mu.Unlock()
    return sum
}

// ⚠️ しかし、deferの方が安全(パニック時も確実に解放)
// パフォーマンスより安全性を優先すべき場合が多い

6.5 defer と panic/recover

func recoverExample() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()

    panic("something went wrong")
}

panic発生時の動作:

[1] panic("...") 実行
    - ランタイムがpanic処理を開始

[2] 現在のGoroutineのdeferチェインを実行
    while g._defer != nil {
        d := g._defer
        g._defer = d.link
        d.fn()  // この中でrecoverが呼ばれるかも

        if recovered {
            // panic終了、通常実行に復帰
            goto normal_flow
        }
    }

[3] 全deferを実行してもrecoverされなければ
    - Goroutine終了
    - プログラムクラッシュ(mainの場合)

_panic構造体:
┌──────────────────┐
│  _panic          │
│   arg: "..."     │ ← panicの引数
│   recovered: no  │ ← recoverフラグ
│   _defer: ...    │ ← 関連defer
└──────────────────┘

7. メソッドの内部実装

7.1 メソッドの基本

// 型定義
type Rectangle struct {
    Width  float64
    Height float64
}

// メソッド(値レシーバ)
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// 使用例
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("Area:", rect.Area())          // 50
fmt.Println("Perimeter:", rect.Perimeter()) // 30

7.2 レシーバの扱い

🔑 重要: メソッドは「第1引数がレシーバの関数」として実装されます。

// メソッド定義
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// コンパイラが変換する内部表現(疑似コード)
func Rectangle_Area(r Rectangle) float64 {
    return r.Width * r.Height
}

// 呼び出し
rect.Area()
// ↓ コンパイラが変換
Rectangle_Area(rect)

メソッド呼び出しの変換:

ソースコード:
  rect := Rectangle{Width: 10, Height: 5}
  area := rect.Area()

コンパイラが生成:
  rect := Rectangle{Width: 10, Height: 5}
  area := Rectangle.Area(rect)
       ↑ レシーバが第1引数

7.3 値レシーバ vs ポインタレシーバ

// 値レシーバ
func (r Rectangle) AreaValue() float64 {
    r.Width = 999  // コピーを変更(元は変わらない)
    return r.Width * r.Height
}

// ポインタレシーバ
func (r *Rectangle) AreaPointer() float64 {
    r.Width = 999  // 元を変更
    return r.Width * r.Height
}

メモリレイアウトと動作:

[値レシーバ]
呼び出し前:
┌───────────────┐
│  rect         │
│   Width: 10   │
│   Height: 5   │
└───────────────┘

rect.AreaValue() 呼び出し:
┌───────────────┐
│  rect         │  ← 元のまま
│   Width: 10   │
│   Height: 5   │
└───────────────┘

スタック:
┌───────────────┐
│  r (コピー)   │
│   Width: 999  │  ← メソッド内で変更
│   Height: 5   │
└───────────────┘

呼び出し後:
  rect.Width == 10  // 変更されていない

[ポインタレシーバ]
呼び出し前:
┌───────────────┐
│  rect         │
│   Width: 10   │
│   Height: 5   │
└───────────────┘

rect.AreaPointer() 呼び出し:
スタック:
┌───────────────┐
│  r (ポインタ) │ ─┐
└───────────────┘  │
                   │
ヒープ/スタック:   │
┌───────────────┐  │
│  rect         │ ←┘
│   Width: 999  │  ← 直接変更
│   Height: 5   │
└───────────────┘

呼び出し後:
  rect.Width == 999  // 変更されている

アセンブリレベルの違い:

; 値レシーバ: rect.AreaValue()
; Rectangle は 16バイト(float64 × 2)
AreaValue:
    ; レシーバをコピー
    mov   rax, [rect]        ; Width をコピー
    mov   rbx, [rect+8]      ; Height をコピー

    ; 処理(コピーを変更)
    mov   rax, 999
    mulsd xmm0, xmm1         ; Width * Height
    ret

; ポインタレシーバ: rect.AreaPointer()
AreaPointer:
    ; RAX = レシーバのアドレス

    ; 処理(ポインタ経由で変更)
    mov   QWORD [rax], 999   ; rect.Width = 999
    movsd xmm0, [rax]        ; Width をロード
    movsd xmm1, [rax+8]      ; Height をロード
    mulsd xmm0, xmm1         ; Width * Height
    ret

7.4 自動的なポインタ変換

💡 Goの便利機能: 値とポインタは自動的に変換されます。

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) AreaValue() float64 {
    return r.Width * r.Height
}

func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// 使用例
rect := Rectangle{Width: 10, Height: 5}

// 値レシーバ: そのまま呼び出せる
area := rect.AreaValue()

// ポインタレシーバ: 値からでも呼び出せる
rect.Scale(2)  // コンパイラが (&rect).Scale(2) に変換

// ポインタからも呼び出せる
ptr := &rect
ptr.AreaValue()  // コンパイラが (*ptr).AreaValue() に変換
ptr.Scale(2)     // そのまま

コンパイラの変換:

ソースコード:
  rect.Scale(2)

レシーバが値、メソッドがポインタ → アドレスを取る:
  (&rect).Scale(2)

ソースコード:
  ptr.AreaValue()

レシーバがポインタ、メソッドが値 → デリファレンス:
  (*ptr).AreaValue()

⚠️ 注意: 変換できない場合

// マップの値は直接アドレスが取れない
m := map[string]Rectangle{
    "key": Rectangle{10, 5},
}

// ❌ コンパイルエラー
m["key"].Scale(2)  // cannot take address of m["key"]

// ✅ 修正版
r := m["key"]
r.Scale(2)
m["key"] = r

7.5 どちらを使うべきか

// ポインタレシーバを使うべき場合:

// 1. レシーバを変更する
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// 2. 大きな構造体のコピーを避ける
type LargeStruct struct {
    data [1000000]int
}

func (l *LargeStruct) Process() {
    // コピーコスト(8MB)を避ける
}

// 3. 一貫性のため(同じ型のメソッドは統一)
type Counter struct {
    count int
}

func (c *Counter) Increment() { c.count++ }
func (c *Counter) Value() int { return c.count }  // 変更しないが、ポインタで統一

コピーコストの比較:

小さな構造体(16バイト):
  値レシーバ:     2 CPU命令(MOV × 2)
  ポインタレシーバ: 1 CPU命令(MOV ptr)
  差: ほぼ無視できる

中程度の構造体(128バイト):
  値レシーバ:     16 CPU命令(MOV × 16)
  ポインタレシーバ:  1 CPU命令
  差: 10-20ns

大きな構造体(8MB):
  値レシーバ:     約2ms(メモリコピー)
  ポインタレシーバ:  1ns(ポインタのみ)
  差: 2,000,000倍!

💡 ガイドライン:

// ✅ 値レシーバを使う場合:
// - 構造体が小さい(3フィールド以下、24バイト以下)
// - 変更しない
// - プリミティブ型のエイリアス

type Point struct {
    X, Y int
}

func (p Point) Distance() float64 {
    return math.Sqrt(float64(p.X*p.X + p.Y*p.Y))
}

// ✅ ポインタレシーバを使う場合:
// - 変更する
// - 構造体が大きい
// - 同じ型の他のメソッドがポインタレシーバ

type Database struct {
    connection *sql.DB
    cache      map[string]interface{}
    config     Config
}

func (db *Database) Query(sql string) (*Result, error) {
    // ...
}

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

🔑 重要: 値とポインタでメソッドセットが異なります。

type Shape interface {
    Area() float64
}

type Scalable interface {
    Scale(float64)
}

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

メソッドセット:

Rectangle(値)のメソッドセット:
  - Area()      ← 値レシーバ

*Rectangle(ポインタ)のメソッドセット:
  - Area()      ← 値レシーバ(自動で使える)
  - Scale()     ← ポインタレシーバ

インターフェース実装:
  var s Shape

  s = Rectangle{10, 5}    // ✅ OK: Area()がある
  s = &Rectangle{10, 5}   // ✅ OK: Area()がある

  var sc Scalable

  sc = Rectangle{10, 5}   // ❌ エラー: Scale()がない
  sc = &Rectangle{10, 5}  // ✅ OK: Scale()がある

理由:

値からはアドレスを取れないため:

Rectangle の Scale() を呼ぶには:
  rect.Scale(2)
  ↓
  (&rect).Scale(2)  ← アドレスが必要

しかし、インターフェース経由では:
  var sc Scalable = Rectangle{10, 5}
  sc.Scale(2)

  sc はインターフェース値(コピー)
  コピーのアドレスを取っても元は変わらない
  → 意図しない動作を防ぐため禁止

7.7 メソッドチェーン

type StringBuilder struct {
    buffer []string
}

func NewStringBuilder() *StringBuilder {
    return &StringBuilder{
        buffer: make([]string, 0),
    }
}

// メソッドチェーン用にポインタを返す
func (sb *StringBuilder) Append(s string) *StringBuilder {
    sb.buffer = append(sb.buffer, s)
    return sb
}

func (sb *StringBuilder) AppendLine(s string) *StringBuilder {
    sb.buffer = append(sb.buffer, s+"\n")
    return sb
}

func (sb *StringBuilder) String() string {
    return strings.Join(sb.buffer, "")
}

// 使用例
result := NewStringBuilder().
    Append("Hello").
    Append(", ").
    Append("World").
    AppendLine("!").
    AppendLine("From Go").
    String()

メソッドチェーンの内部動作:

NewStringBuilder() の戻り値を tmp1 として:
  tmp1 := NewStringBuilder()
  tmp2 := tmp1.Append("Hello")      // tmp2 == tmp1
  tmp3 := tmp2.Append(", ")         // tmp3 == tmp2 == tmp1
  tmp4 := tmp3.Append("World")      // tmp4 == tmp3 == tmp2 == tmp1
  tmp5 := tmp4.AppendLine("!")      // tmp5 == ...
  tmp6 := tmp5.AppendLine("From Go")
  result := tmp6.String()

全て同じ *StringBuilder を指している

7.8 組み込み型へのメソッド追加

// 型エイリアスを作ってメソッドを追加
type MyInt int

func (m MyInt) Double() MyInt {
    return m * 2
}

func (m MyInt) IsEven() bool {
    return m%2 == 0
}

// 使用例
var num MyInt = 5
fmt.Println(num.Double())  // 10
fmt.Println(num.IsEven())  // false

var num2 MyInt = 6
fmt.Println(num2.IsEven()) // true

8. 関数とメソッドのベストプラクティス

8.1 短く保つ

// ✅ 良い例: 単一責任
func validateEmail(email string) error {
    if !strings.Contains(email, "@") {
        return errors.New("invalid email: missing @")
    }
    return nil
}

func sendEmail(to, subject, body string) error {
    if err := validateEmail(to); err != nil {
        return err
    }
    // メール送信ロジック
    return nil
}

8.2 エラーを早く返す

// ✅ 良い例: アーリーリターン
func processFile(filename string) error {
    if filename == "" {
        return errors.New("filename is empty")
    }

    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    data, err := io.ReadAll(file)
    if err != nil {
        return err
    }

    // 処理
    return nil
}

8.3 インターフェースを受け取り、構造体を返す

// ✅ 良い例
type Reader interface {
    Read([]byte) (int, error)
}

// インターフェースを受け取る
func processData(r Reader) (*Result, error) {
    // 処理
    return &Result{}, nil
}

// 具体的な型を返す
func NewProcessor() *Processor {
    return &Processor{}
}

9. 自己確認問題

以下の質問に答えて、理解度を確認しましょう。

基本問題

  • CALL命令は内部で何をしますか?
- ヒント: スタックとRIPレジスタ

  • スタックフレームの主要な構成要素を3つ挙げてください。
  • Go 1.17以降、整数引数は最初にどのレジスタに配置されますか?
  • 複数戻り値の場合、最初の2つはどのレジスタに配置されますか?
  • 応用問題

  • クロージャがキャプチャする変数は、スタックとヒープのどちらに配置されますか?なぜですか?
  • defer文のオーバーヘッドは約何ナノ秒ですか(Go 1.22)?
  • 次のコードで、rect.Widthの値は何になりますか?
   rect := Rectangle{Width: 10, Height: 5}
   rect.AreaValue()  // func (r Rectangle) AreaValue() { r.Width = 999; ... }
   fmt.Println(rect.Width)
   

  • 値レシーバとポインタレシーバ、どちらを選ぶべき基準を3つ挙げてください。
  • 発展問題

  • 次のコードはコンパイルエラーになります。なぜですか?
   type Scalable interface {
       Scale(float64)
   }

   type Rectangle struct { Width, Height float64 }
   func (r *Rectangle) Scale(f float64) { r.Width *= f; r.Height *= f }

   var s Scalable = Rectangle{10, 5}  // エラー
   

  • defer文が複数ある場合、実行順序はどうなりますか?内部のデータ構造の名前も答えてください。
  • レジスタ渡しとスタック渡し、なぜレジスタの方が高速ですか?具体的な理由を2つ挙げてください。
  • 次のコードのクロージャは何バイトのメモリを消費しますか?(64bit環境)
    func makeCounter() func() int {
        count := 0
        total := 0
        return func() int {
            count++
            total += count
            return total
        }
    }
    

実践問題

  • 次のコードのベンチマーク結果を予想してください:
    func normalFunc() int { return 42 }

    func makeClosure() func() int {
        x := 42
        return func() int { return x }
    }

    // どちらが速い?何倍?
    

  • Cgoを使った関数呼び出しは、純粋なGo関数呼び出しより約何倍遅いですか?
  • 次のコードを最適化してください(パフォーマンス重視):
    func process(items []int) {
        for _, item := range items {
            mu.Lock()
            defer mu.Unlock()
            // 処理
        }
    }
    

10. まとめ

この章では、Goの関数とメソッドの内部実装まで深く学びました。

重要ポイント

🔑 関数呼び出し:

  • CALL命令が戻りアドレスをPUSH
  • スタックフレームで引数・ローカル変数を管理
  • RET命令で戻りアドレスをPOPして復帰

🔑 Go ABI:

  • Go 1.17以降はレジスタベース
  • 整数9個、浮動小数点15個までレジスタ
  • レジスタ渡しで20-30%高速化

🔑 クロージャ:

  • キャプチャ変数はヒープにエスケープ
  • 関数値は構造体として実装
  • 間接呼び出しで若干のオーバーヘッド

🔑 defer:

  • LIFOスタック(後入れ先出し)
  • 約10nsのオーバーヘッド(Go 1.22)
  • panic時も確実に実行される

🔑 メソッド:

  • 第1引数がレシーバの関数として実装
  • 値レシーバはコピー、ポインタレシーバは参照
  • 値とポインタのメソッドセットは異なる

Goらしい書き方

💡 ベストプラクティス:

  • エラーを返して適切に処理
  • 値レシーバは不変操作、ポインタレシーバは変更操作
  • 短く焦点を絞った関数
  • インターフェースを受け取り、構造体を返す
  • deferで確実にリソース解放

⚠️ 避けるべきパターン:

  • 長い関数(50行以上)
  • 裸のreturnの乱用
  • ホットパスでの不要なdefer
  • 大きな構造体の値レシーバ

次のステップ

次の章では、配列とスライスについて学びます。メモリレイアウトから内部実装まで、徹底的に理解していきましょう。