Skip to content

บทที่ 2 — Control Flow

← บทที่ 1 | สารบัญ | บทที่ 3 →

หลังจบบท คุณจะ:

  • เขียน if / for / switch แบบสำนวน Go แท้ (idiomatic = สำนวนการเขียนที่ชาว Go นิยม ถือว่า "เขียนแบบ Go แท้")
  • ใช้ for 4 รูปแบบ (Go มี loop เดียว!)
  • ใช้ defer + goto (เผื่อจำเป็น)
  • เข้าใจ break/continue/labels

1. if / else

เริ่มจากการตัดสินใจพื้นฐานที่สุด — if/else Go มีกฎต่างจากภาษาตระกูล C เล็กน้อย ดูรายละเอียดในตาราง "กฎ Go" ด้านล่าง:

go
x := 10

if x > 5 {
    fmt.Println("big")
} else if x > 0 {
    fmt.Println("positive small")
} else {
    fmt.Println("zero or negative")
}

กฎ Go

text
✅ no parentheses around condition
✅ braces { } บังคับ — แม้ 1 บรรทัด
✅ { ต้องอยู่บรรทัดเดียวกับ if/else (gofmt บังคับ)
go
// ❌
if (x > 5)        // เกินไป
{                 // brace ต้องอยู่บรรทัดเดียวกัน
    ...
}

if x > 5 {        // ✅
    ...
}

2. if with Initial Statement — Idiomatic Go

(ต่างจาก C#: C# ไม่มี syntax นี้ — ใกล้เคียงสุดคือประกาศตัวแปรก่อน if แยกบรรทัด; Go รวมประกาศ+เงื่อนไขในบรรทัดเดียว ทำให้ scope แคบกว่า)

go
// Common pattern: declare + check
if err := doSomething(); err != nil {
    return err
}
// err out of scope here ← good!
go
// Without init (verbose)
err := doSomething()
if err != nil {
    return err
}
// err still in scope — pollute

หมายเหตุสำหรับมือใหม่: err คือค่าข้อผิดพลาดที่ฟังก์ชันคืนกลับมา — เราจะเรียนเรื่องนี้เต็ม ๆ ในบท 03 Functions และบท 07 Error Handling ตอนนี้แค่รู้ว่ารูปแบบนี้มีอยู่ก็พอ

ทำไม developer (นักพัฒนา) Go ชอบ pattern (รูปแบบการเขียน) นี้?

  • err มี scope (ขอบเขตการมองเห็นตัวแปร) แคบลง
  • ตัวแปรไม่หลุดออกไปนอก if (ลดตัวแปรค้างในขอบเขตภายนอก — pollute outer scope)
  • เป็น idiom (สำนวนที่ชาว Go นิยม) — อ่านแล้วรู้ทันที

Multiple Use Case

go
if v, ok := m[key]; ok {
    fmt.Println(v)
}

if user, err := getUser(id); err == nil {
    fmt.Println(user)
}

3. for — Loop เดียวใน Go

Go มี loop ตัวเดียว: for — แต่ใช้ได้ 4 แบบ

A. Classic C-style

go
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

B. While-style

go
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

→ Go ไม่มี while keyword — for condition แทน

C. Infinite Loop

go
for {
    // forever
    if shouldStop() {
        break
    }
}

→ ใช้กับ server loop (ลูปของเซิร์ฟเวอร์ที่รอรับ request เรื่อย ๆ), event loop (ลูปรอเหตุการณ์เข้ามาแล้วทำงาน)

D. for range (collection)

go
nums := []int{1, 2, 3, 4, 5}

for i, v := range nums {
    fmt.Printf("index=%d value=%d\n", i, v)
}

// only index
for i := range nums {
    fmt.Println(i)
}

// only value (discard index)
for _, v := range nums {
    fmt.Println(v)
}

// map
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
    fmt.Printf("%s = %d\n", k, v)
}

// string (rune by rune)
for i, r := range "héllo" {
    fmt.Printf("%d: %c\n", i, r)
}
// 0: h
// 1: é     (note: index jumps because é = 2 bytes)
// 3: l
// 4: l
// 5: o

(เดี๋ยวบท 04 Collections อธิบายเรื่อง rune/byte เต็ม ๆ ว่าทำไม index ถึงกระโดดข้าม — ตอนนี้แค่สังเกตว่า é กิน 2 ช่องพอ)

⚙️ Range Over Channel (ขั้นสูง — ข้ามได้ บท 08)

range ยังใช้กับ channel (ช่องสื่อสารระหว่าง goroutine) ได้ด้วย — ข้ามส่วนนี้ไปก่อน ถ้ายังไม่รู้จัก channel เดี๋ยวบท 08 Concurrency อธิบายเต็ม

go
// channel (ช่องสื่อสาร — เรียนบท 08)
for msg := range ch {
    fmt.Println(msg)
}

E. Range Over Int (Go 1.22+)

go
// loop 0..9
for i := range 10 {
    fmt.Println(i)
}

// equivalent
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

→ Concise version of for-i


4. break + continue

ภายใน loop เราควบคุมการวนได้ด้วยคำสั่งสองตัว: continue ข้ามไปรอบถัดไปทันที (ไม่ทำโค้ดที่เหลือในรอบนั้น) ส่วน break ออกจาก loop เลย:

go
for i := 0; i < 10; i++ {
    if i == 3 {
        continue   // skip rest of iteration
    }
    if i == 7 {
        break       // exit loop
    }
    fmt.Println(i)
}
// 0 1 2 4 5 6

5. Labels — Break / Continue Specific Loop

ปกติ break ออกจาก loop ชั้นในสุดเท่านั้น แต่ถ้ามี loop ซ้อนกันแล้วอยากออกจากทั้งหมดทีเดียวล่ะ? ให้ตั้ง "label" (ชื่อ) ให้ loop ชั้นนอก แล้วสั่ง break outer — ใช้ไม่บ่อยแต่จำเป็นในบางกรณี:

go
outer:
for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 6 {
            break outer       // break out of BOTH loops
        }
        fmt.Println(i, j)
    }
}

→ ใช้น้อย แต่จำเป็นบาง case


6. switch — มากกว่า if/else if/else

go
day := "Monday"

switch day {
case "Monday":
    fmt.Println("Start of week")
case "Friday":
    fmt.Println("TGIF!")
case "Saturday", "Sunday":      // multiple values
    fmt.Println("Weekend")
default:
    fmt.Println("Midweek")
}

กฎ Go

  • ไม่ต้องมี break — auto break (ต่างจาก C#/C/Java ที่ต้องเขียน break เอง — C# บังคับ break/return ทุก case ห้าม fall-through เงียบ ส่วน Go เติมให้อัตโนมัติ และถ้าอยากรันต่อ case ถัดไปต้องเขียน fallthrough ชัดเจน)
  • ✅ Multiple values: case "a", "b", "c":
  • (ขั้นสูง — ข้ามได้) ตัวเลขเปล่า ๆ ใน case เช่น case 1: ใช้ได้แม้ตัวแปรที่ switch จะเป็น type พิเศษอย่าง int64 ก็ตาม (ไม่ต้องเขียน case int64(1):) เพราะ Go แปลง type ให้อัตโนมัติ

Fallthrough — explicit ถ้าต้องการ

go
switch x {
case 1:
    fmt.Println("one")
    fallthrough     // run next case too
case 2:
    fmt.Println("two")
}

ใช้น้อยมาก — แค่ flag ที่เห็นได้ใน code


7. switch with Initial Statement

เช่นเดียวกับ if เราใส่คำสั่งเริ่มต้นใน switch ได้ (ประกาศตัวแปรก่อนคั่นด้วย ;) — ทำให้ตัวแปร (เช่น err) มี scope แคบอยู่แค่ใน switch นี้ ไม่รั่วไปข้างนอก:

go
// ErrNotFound, ErrInternal เป็น sentinel error (ค่า error ที่ประกาศไว้ล่วงหน้า) ที่เราประกาศเอง — เดี๋ยวบท 07 อธิบาย
switch err := doSomething(); err {
case nil:
    fmt.Println("ok")
case ErrNotFound:
    fmt.Println("not found")
case ErrInternal:
    fmt.Println("server error")
default:
    fmt.Printf("unknown: %v\n", err)
}

8. switch Without Tag (if-else chain)

switch ที่ไม่ใส่ค่าให้ตรวจ (ไม่มี expression ต่อท้าย) จะกลายเป็น "if-else if chain" ที่อ่านง่ายกว่า — แต่ละ case เป็นเงื่อนไข boolean เต็มรูปแบบ เหมาะเวลาเทียบช่วงค่า:

go
score := 85

switch {                       // no expression after switch
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B")
case score >= 70:
    fmt.Println("C")
default:
    fmt.Println("F")
}

→ readability ดีกว่า if/else if/else if/else


9. Type Switch

"Type switch" เป็น switch รูปแบบพิเศษที่ใช้ตรวจว่าค่าใน interface (อินเทอร์เฟซ — เดี๋ยวบท 06 อธิบายเต็ม) เป็น type อะไรจริง ๆ ตอน runtime (ขณะโปรแกรมกำลังรัน)

ใช้รูป i.(type) (เขียนได้เฉพาะใน switch เท่านั้น) แล้วแตกเคสตามชนิด เจอบ่อยเวลาทำงานกับ any (= interface{} ตั้งแต่ Go 1.18 — ชนิดที่รับค่าอะไรก็ได้)

โซนขั้นสูง — มือใหม่ข้ามได้ ส่วนนี้ใช้ interface ที่ยังไม่ได้เรียน ตอนนี้แค่รู้ว่า "Go เช็คชนิดข้อมูลจริงตอนรันได้" ก็พอ เดี๋ยวกลับมาอ่านหลังเรียนบท 06 Interfaces จะเข้าใจทันที

go
var i any = "hello"        // any = interface{} — รับค่าอะไรก็ได้

switch v := i.(type) {
case int:
    fmt.Printf("int: %d\n", v)
case string:
    fmt.Printf("string: %s\n", v)
case bool:
    fmt.Printf("bool: %t\n", v)
default:
    fmt.Printf("unknown type: %T\n", v)
}

.(type) ใช้ได้ใน switch เท่านั้น — เช็ค dynamic type ของ interface


10. defer — Run Later

defer เป็นคีย์เวิร์ดเด่นของ Go — มันบอกว่า "เลื่อนการทำคำสั่งนี้ไปทำตอนฟังก์ชันกำลังจะ return" นิยมใช้เก็บกวาดทรัพยากร (ปิดไฟล์, ปลดล็อก) ให้แน่ใจว่าทำเสมอแม้จะ return หลายจุด:

(ต่างจาก C#: deferusing/finally แต่ ต่างกันสำคัญusing/finally ทำงานตอนออกจาก block {} (block-scoped) ส่วน defer ทำงานตอนออกจากทั้งฟังก์ชัน (function-scoped) นี่คือเหตุผลที่ defer ในลูปจะสะสมรอจนฟังก์ชันจบถึงจะรันทั้งหมด — ต่างจาก using ที่ไม่มีปัญหานี้เพราะผูกกับ scope ของ block)

go
func main() {
    fmt.Println("Start")
    defer fmt.Println("Deferred")
    fmt.Println("End")
}
// Output:
// Start
// End
// Deferred

defer = "run นี้ตอน function return"

ใช้สำหรับ cleanup

go
func readFile(path string) {
    file, err := os.Open(path)
    if err != nil {
        return
    }
    defer file.Close()       // guaranteed close
    
    // ... do stuff with file
    // file.Close() จะถูกเรียกเมื่อ function จบ
}

Multiple defer — LIFO Stack

go
func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
}
// Output:
// 3
// 2
// 1

Defer + Arguments — evaluated ตอน defer

go
func main() {
    x := 10
    defer fmt.Println("x =", x)    // captures x = 10 NOW
    x = 20
}
// Output: x = 10
go
// ใช้ closure (ฟังก์ชันที่จำตัวแปรรอบตัวได้ — เดี๋ยวบท 03 อธิบาย) เพื่อให้ผูกค่าตอนหลัง
func main() {
    x := 10
    defer func() { fmt.Println("x =", x) }()   // อ้างถึงตัว x จริง (capture by reference) ไม่ใช่ค่าตอนนี้
    x = 20
}
// Output: x = 20

Common Patterns

ตรงนี้แค่ดูรูปแบบ defer ที่เจอบ่อยพอ — รายละเอียดของ Mutex (ตัวล็อกกันชนกันใน goroutine) อยู่บท 08 Concurrency, panic / recover อยู่บท 07 Error Handling

go
// File
defer file.Close()

// Mutex (ตัวล็อก — เดี๋ยวบท 08 อธิบาย)
mu.Lock()
defer mu.Unlock()

// HTTP body
defer resp.Body.Close()

// Time function
defer timeTrack(time.Now(), "myFunc")

// Recover from panic (กู้คืนจาก panic = โปรแกรมพัง — รายละเอียดบท 07; ตรงนี้แค่เห็นรูปแบบ defer + recover ที่ใช้บ่อย)
defer func() {
    if r := recover(); r != nil {
        log.Printf("recovered: %v", r)
    }
}()

11. goto (rare)

go
func search() {
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if found(i, j) {
                goto done
            }
        }
    }
done:
    fmt.Println("done")
}

ใช้น้อยมาก — มัก break/continue + labels ดีกว่า
Idiomatic Go ไม่ค่อยใช้ goto


12. Range Over Function (Go 1.23+) — Modern

โซนขั้นสูงมาก — มือใหม่ข้ามไปก่อนได้เลย แล้วค่อยกลับมาอ่านหลังจบบท 3 หัวข้อนี้ใช้ฟังก์ชันซ้อนฟังก์ชัน (closure — ยังไม่ได้สอน จะเรียนบท 03 Functions) รูปแบบที่ใช้ (ดูโค้ดด้านล่าง) อาจดูน่ากลัวถ้ายังไม่รู้จัก closure

ตั้งแต่ Go 1.23 เราใช้ for range วนบน "ฟังก์ชัน iterator (ตัววนซ้ำ)" ที่เราเขียนเองได้

ข้อดีคือ lazy — คำนวณค่าทีละตัวตอนที่ for-range ขอเท่านั้น ไม่สร้างข้อมูลทั้งก้อนในหน่วยความจำล่วงหน้า ประหยัด RAM มากเวลาข้อมูลจำนวนมาก:

go
// Iterator function
func count(n int) func(yield func(int) bool) {
    return func(yield func(int) bool) {
        for i := 0; i < n; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

// Use
for i := range count(5) {
    fmt.Println(i)
}
// 0 1 2 3 4

→ Pattern สำหรับ lazy iteration (like Python generator)


13. Pattern: Loop + State

ตัวอย่างด้านล่างสมมติว่ามี names []string (เช่น []string{"Anna", "Ben", "Cara"}) และ nums []int (เช่น []int{-3, 5, 8, -1}) ประกาศไว้แล้ว — โฟกัสที่รูปแบบ (pattern) ของ loop เอง ไม่ต้องกังวลว่าตัวแปรมาจากไหน

Find First

go
target := "Anna"
found := false

for _, name := range names {
    if name == target {
        found = true
        break
    }
}

if found {
    fmt.Println("Found!")
}

Or — Use Function

go
func contains(slice []string, target string) bool {
    for _, s := range slice {
        if s == target {
            return true
        }
    }
    return false
}

if contains(names, "Anna") {
    fmt.Println("Found!")
}

Count

go
count := 0
for _, n := range nums {
    if n > 0 {
        count++
    }
}

Filter (build new slice)

go
var positive []int
for _, n := range nums {
    if n > 0 {
        positive = append(positive, n)
    }
}

Map (transform)

go
doubled := make([]int, len(nums))
for i, n := range nums {
    doubled[i] = n * 2
}

Reduce (sum)

go
sum := 0
for _, n := range nums {
    sum += n
}

Go ไม่มี .map() / .filter() / .reduce() แบบ functional (สไตล์ฟังก์ชัน — เขียนต่อท่อกัน เช่น JS, Python, Java Stream) ภาษาอื่น — เขียน loop ตรง ๆ
(ต่างจาก C#: ก็ไม่มี LINQ (Where/Select/Aggregate) ที่ dev .NET ใช้ทุกวันด้วย — ต้องเขียน loop ตรง ๆ แทน Go 1.23+ iterators ช่วยได้บ้างแต่ไม่ครอบคลุมเท่า LINQ)
ปี 2026 มี Go 1.23+ iterators ช่วยได้บ้าง — แต่ idiomatic = loop


14. Common Pitfalls

❌ Loop Variable Capture (Go ก่อน 1.22)

ตัวอย่างนี้ใช้ closure (ฟังก์ชันที่จำตัวแปรรอบตัวได้) ที่ยังไม่ได้เรียน — ตอนนี้แค่ "รู้ว่ามีกับดักนี้" ก็พอ เดี๋ยวบท 03 Functions อธิบาย closure เต็ม ถ้าส่วนนี้ดูงงให้ข้ามไปก่อนได้เลย

go
// Go < 1.22 — มี bug นี้
funcs := []func(){}
for i := 0; i < 3; i++ {
    funcs = append(funcs, func() {
        fmt.Println(i)
    })
}

for _, f := range funcs {
    f()
}
// Output (Go < 1.22): 3 3 3 (i shared!)
// Output (Go >= 1.22): 0 1 2 (per-iteration scope)

→ Go 1.22+ แก้แล้ว — แต่ระวังถ้าใช้ version เก่า:

go
// Fix สำหรับ Go < 1.22
for i := 0; i < 3; i++ {
    i := i    // shadow — new variable per iteration
    funcs = append(funcs, func() {
        fmt.Println(i)
    })
}

❌ Modify Slice While Ranging

go
s := []int{1, 2, 3, 4, 5}
for i, v := range s {
    if v == 3 {
        s = append(s[:i], s[i+1:]...)    // 😱 modify while iterating
    }
}

→ Build new slice instead

❌ for-range Value = Copy

go
type User struct{ Name string }

users := []User{{"Anna"}, {"Ben"}}

for _, u := range users {
    u.Name = "Modified"     // ❌ modify copy — original unchanged
}
fmt.Println(users)           // [{Anna} {Ben}]

// ✅ Use index
for i := range users {
    users[i].Name = "Modified"
}

❌ Forget to handle default in switch

go
switch status {
case "ACTIVE":
    ...
case "INACTIVE":
    ...
// no default — silent ignore unknown status
}

// ✅ Add default
switch status {
case "ACTIVE":
    ...
case "INACTIVE":
    ...
default:
    return fmt.Errorf("unknown status: %s", status)
}

15. ตัวอย่างจริง — FizzBuzz

มาประยุกต์ใช้ control flow กับโจทย์คลาสสิก "FizzBuzz" — พิมพ์เลข 1-100 แต่แทนตัวที่หาร 3 ลงตัวด้วย "Fizz", หาร 5 ด้วย "Buzz", หารทั้งคู่ด้วย "FizzBuzz" สังเกตการใช้ switch แบบไม่มี tag ที่อ่านสะอาดกว่า if ซ้อน:

go
package main

import "fmt"

func main() {
    for i := 1; i <= 100; i++ {
        switch {
        case i%15 == 0:
            fmt.Println("FizzBuzz")
        case i%3 == 0:
            fmt.Println("Fizz")
        case i%5 == 0:
            fmt.Println("Buzz")
        default:
            fmt.Println(i)
        }
    }
}

16. ตัวอย่างจริง — Number Guesser

ตัวอย่างเกมทายเลขที่รวม control flow หลายอย่างเข้าด้วยกัน — for {} วนไม่รู้จบ, อ่าน input จากผู้ใช้, จัดการ error ตอนแปลงเลข, ใช้ continue ข้ามเมื่อกรอกผิด และ switch/return เมื่อทายถูก:

go
package main

import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"
)

func main() {
    // Go 1.20+ auto-seed ให้เรียบร้อย — รุ่นเก่าจะได้เลขเดิมทุกรอบ
    target := rand.Intn(100) + 1
    reader := bufio.NewReader(os.Stdin)
    attempts := 0
    
    for {
        fmt.Print("Guess (1-100): ")
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("Bye!")    // stdin ปิด (EOF / Ctrl-D)
            return
        }
        input = strings.TrimSpace(input)
        
        if input == "quit" {
            fmt.Println("Bye!")
            return
        }
        
        guess, err := strconv.Atoi(input)
        if err != nil {
            fmt.Println("Invalid number")
            continue
        }
        
        attempts++
        
        switch {
        case guess < target:
            fmt.Println("Too low")
        case guess > target:
            fmt.Println("Too high")
        default:
            fmt.Printf("Correct! Took %d attempts.\n", attempts)
            return
        }
    }
}

17. Performance — for vs range

go
nums := make([]int, 1_000_000)

// Both same performance (compiler optimize)
for i := 0; i < len(nums); i++ {
    _ = nums[i]
}

for i := range nums {
    _ = nums[i]
}

for _, v := range nums {
    _ = v        // คัดลอก v ทุกรอบ — ตรงนี้เป็น int เลยไม่มีผลด้านความเร็ว แต่ถ้า element เป็น struct ขนาดใหญ่ การคัดลอกทุกรอบจะช้าลงเห็นได้ชัด
}

→ For most cases, ใช้ range — readability ดีกว่า

→ For big struct, ใช้ index ก็ได้ ถ้า perf สำคัญ:

go
for i := range users {
    user := &users[i]    // pointer, no copy
    process(user)
}

18. Idioms ที่ควรรู้

รวม "สำนวน" (idiom) ที่เป็นเอกลักษณ์และพบบ่อยมากในโค้ด Go จริง — เช่นการเช็คค่าใน map พร้อมประกาศตัวแปรใน if (if v, ok := m[key]; ok) และรูปแบบรับ-เช็ค error ที่เห็นแทบทุกฟังก์ชัน คุ้นกับสำนวนเหล่านี้แล้วจะอ่านโค้ด Go คนอื่นออกทันที:

Initial assignment in if

go
if v, ok := m[key]; ok {
    use(v)
}

Multiple return + check error

go
result, err := fn()
if err != nil {
    return err
}
use(result)

Switch type

go
switch v := i.(type) {
case string:
    ...
}

Defer for cleanup

go
defer cleanup()

Loop pattern

go
for ; condition; update {}   // 1 statement
for condition {}              // 1 statement
for {}                         // forever
for range collection {}        // iterate

19. Checkpoint

🛠️ Checkpoint 2.1 — FizzBuzz
เขียน FizzBuzz เลข 1-100 — ใช้ switch ไม่ใช้ if/else

🛠️ Checkpoint 2.2 — Number Guesser (เกมทายเลข)
ทำ checkpoint ข้อ 16 — เพิ่ม:

  • จำกัดจำนวนครั้งที่ทาย (attempts) 10 ครั้ง
  • บันทึกสถิติดีที่สุด (high score = ใช้จำนวนครั้งน้อยสุด)

🛠️ Checkpoint 2.3 — Word Count (นับคำ)
รับ string จาก stdin → นับจำนวนคำ + พิมพ์คำที่ไม่ซ้ำ (unique word) พร้อมจำนวนครั้งที่เจอ

🛠️ Checkpoint 2.4 — Defer Practice (ฝึกใช้ defer)
เขียน function ที่:

  • เปิดไฟล์
  • อ่าน + ประมวลผล (process)
  • ใช้ defer file.Close()
  • ทดสอบกรณีเกิด error ระหว่างทาง — ไฟล์ต้องยังถูกปิด (close) อยู่ดี

🛠️ Checkpoint 2.5 — Range Pitfall (กับดักของ range)

  • เขียนโค้ดที่มี slice ของ struct แล้วลองแก้ค่าผ่าน range
  • ดูว่ามันทำงานยังไง (ทำไมแก้ไม่ติด)
  • แก้ให้ถูก (ใช้ index)

20. สรุปบท

if ไม่มี () แต่บังคับ {} แม้ 1 บรรทัด
if with init = idiomatic: if err := fn(); err != nil { ... }
for เป็น loop เดียว — ใช้ได้ 4-5 รูปแบบ (C-style, while, infinite, range, range int)
range over slice/map/string/channel/int (1.22+) / function (1.23+)
switch ไม่มี implicit fallthrough — ไม่ต้องเขียน break ก็จบ case อัตโนมัติ (แต่ break ยังใช้ได้ถ้าจำเป็น เช่น ออกจาก switch ข้างใน loop; ใช้ fallthrough ถ้าต้องการรันต่อ)
switch { case condition: ... } = if-else if chain readable
Type switch: switch v := i.(type)
defer = run ตอน function return — LIFO, capture argument ตอน defer
✅ Loop variable capture แก้แล้วใน Go 1.22
✅ for-range value = copy — ใช้ index ถ้า modify struct


← บทที่ 1 | บทที่ 3 → Functions