Skip to content

Latest commit

 

History

History
92 lines (68 loc) · 3.12 KB

0003-go-for-range-pointer.md

File metadata and controls

92 lines (68 loc) · 3.12 KB

ดึง Pointer ใน for range ใน Go ต้องระวัง

มีหลายครั้งที่ function อาจจะส่ง slice ของ struct ออกมา แล้วเราต้องการที่จะ index สิ่งนั้นลง map

package main

import "fmt"

type A struct {
    Name  string
    Value int
}

func listA() []A {
    return []A{
        {"A", 1},
        {"B", 2},
        {"C", 3},
    }
}

func main() {
    list := listA()
    nameToA := make(map[string]*A)
    for _, a := range list {
        nameToA[a.Name] = &a
    }

    fmt.Println("A:", nameToA["A"].Value)
    fmt.Println("B:", nameToA["B"].Value)
    fmt.Println("C:", nameToA["C"].Value)
}

ดู code ข้างบนดี ๆ ก็เหมือนจะไม่มีอะไร แต่พอลองรันเท่านั้นแหละ !!!

$ go run main.go
A: 3
B: 3
C: 3

ทำไมถึงเป็นแบบนี้ ?

เพราะว่า for _, a := range list คือการ copy ค่าใน list ออกมาใส่ตัวแปร a เพราะว่า list เก็บเป็น slice ของ value (แน่นอนว่าถ้า list เป็น slice ของ pointer จะไม่มีปัญหานี้เกิดขึ้น)

พอวน loop รอบถัดไป ตัวแปร a ก็จะถูก replace ด้วยค่าต่อไปใน list ทำให้ค่าของตัวแปร a เปลี่ยน แต่ address ของตัวแปร a อยู่ที่เดิม

การที่เราเขียน nameToA[a.Name] = &a หมายถึงให้เก็บ addess ของตัวแปร a

เราสามารถแก้ปัญหานี้ได้ 3 วิธี คือ

  1. for range index แทน

    for i := range list {
        nameToA[list[i].Name] = &list[i]
    }
  2. copy ค่าออกมาใส่ในตัวแปรที่อยู่ใน scope ของ for

    for _, a := range list {
        a := a
        nameToA[a.Name] = &a
    }

    การเขียน a := a คือการที่เรา copy ค่าเข้ามาในตัวแปรใหม่ ที่ชื่อซ้ำกับตัวแปรเดิม (shadow variable)

    ถ้าเราไม่อยาก shadow variable สามารถตั้งเป็นชื่ออื่นได้

    for _, a := range list {
        b := a
        nameToA[b.Name] = &b
    }
  3. ไม่ต้องเก็บ pointer ใน map สิ

    nameToA := make(map[string]A)
    for _, a := range list {
        nameToA[a.Name] = a
    }

ดังนั้น เมื่อไรก็ตามที่เราต้องใช้ for range กับ slice ของ value ต้องระวังให้ดี ไม่อย่างนั้นอาจจะเจอปัญหานี้ได้