Types & Data
แก่นของโมดูลนี้: pass by value เสมอ
หัวข้อที่มีชื่อว่า “แก่นของโมดูลนี้: pass by value เสมอ”ทุกการเรียก function ใน Go copy argument ของมันเสมอ ไม่มีข้อยกเว้น ไม่มี reference-passing mode ที่ซ่อนอยู่ และไม่มีทาง opt out ได้เลย สิ่งที่ทำให้งงในตอนแรกคือ type บางตัวเป็น handle เล็ก ๆ ที่บังเอิญชี้ไปยังข้อมูลที่แชร์กัน — การ copy handle ก็ยังเป็นการ copy อยู่ดี มันแค่ทิ้งข้อมูลที่มันชี้ไปให้แชร์กันต่อ ทุกความเซอร์ไพรส์ในโมดูลนี้ (ทำไม slice mutation ถึงมองเห็นได้ข้าม function call แต่ append ไม่ ทำไมการเขียน map ในฟังก์ชันถึงกระทบ caller ทำไมการเขียน struct field ถึงไม่กระทบ) ล้วนมาจากข้อเท็จจริงเดียวนี้ทั้งนั้น เรียนมันที่นี่ก่อน แล้ว Structs, Slices, Maps, และ Pointers แต่ละบทจะกลายเป็นแค่ “อะไรถูก copy” ไม่ใช่กฎสี่ข้อที่ไม่เกี่ยวข้องกันที่ต้องท่องจำ
ภาพรวม Type ใน Go
หัวข้อที่มีชื่อว่า “ภาพรวม Type ใน Go”ทุกค่าใน Go มี type แบบ static ที่กำหนดตั้งแต่ compile time ซึ่ง Go แบ่ง type ออกเป็นสองกลุ่มหลัก:
Value types — ตัวแปรเก็บข้อมูลโดยตรง การ assign ค่าจะ copy ข้อมูลทั้งหมด ได้แก่ boolean, ตัวเลข (int, int64, float64, uint8, …), และ string รวมถึง composite value types อย่าง array และ struct
Reference-like types — ตัวแปรเก็บ handle ขนาดเล็กที่ชี้ไปยังข้อมูลจริงซึ่งถูก allocate ไว้ที่อื่น การ assign จะ copy ตัว handle เอง ไม่ใช่ข้อมูลข้างใน — ส่วนนี้เป็น copy ที่ถูกต้อง เหมือน copy ค่าอื่น ๆ ทั่วไป slice (header แบบ pointer + len + cap), map และ channel (pointer เดียว), function, และ pointer (1 word) ล้วนทำงานแบบนี้ และเพราะ handle ทั้งสอง copy ยังชี้ไปข้อมูลชุดเดียวกัน การ mutation ผ่าน handle หนึ่งจะมองเห็นได้ผ่านอีก handle ด้วย string ก็สร้างด้วยวิธีเดียวกันภายใน — header แบบ pointer + len — แต่พฤติกรรมจากภายนอกเหมือน value type: ข้อมูลใน string เป็น immutable จึงไม่มีทางเกิด shared-mutation ที่น่าแปลกใจแบบ slice กับ map
ความ immutable นั่นแหละคือเหตุผลที่ []byte(s) ต้อง copy: การแปลง string เป็น byte slice ให้บางอย่างที่ mutate ได้กับเรา และวิธีเดียวที่จะรับประกันว่า string ต้นฉบับยังคง immutable ต่อไปคือให้ backing array ของตัวเอง ไม่ใช่แค่ view เข้าไปใน string:
s := "hello"b := []byte(s)b[0] = 'H'fmt.Println(s, string(b)) // hello Hello — s never changed; b was an independent copy from the startverify บน 1.26.5 แล้ว: ได้ hello Hello เป๊ะ การแปลงกลับด้วย string(b) ก็ copy อีกครั้งด้วยเหตุผลย้อนกลับกัน — เมื่ออะไรก็ตามเป็น string แล้ว ไม่มีอะไรจะ mutate มันผ่าน handle อื่นได้อีกเลย ดังนั้น runtime จึงไม่สามารถแค่เปลี่ยน label backing array ของ b ให้เป็น string โดยไม่ copy ก่อนได้
flowchart TD
Root["Go Type Taxonomy"]
Root --> Value["Value types (copy on assign)"]
Root --> Ref["Reference-like (header copied)"]
Value --> V1["bool, int, float64, string"]
Value --> V2["[N]T (array)"]
Value --> V3["struct { ... }"]
Ref --> R1["*T (pointer)"]
Ref --> R2["[]T (slice)"]
Ref --> R3["map[K]V"]
Ref --> R4["chan T, func(...)"] เนื้อหาในโมดูลนี้
หัวข้อที่มีชื่อว่า “เนื้อหาในโมดูลนี้”| บทเรียน | หัวข้อ |
|---|---|
| หน้านี้ | ภาพรวม Type และโปรแกรมตัวอย่างแรก |
| Structs | การนิยาม struct, embedding, การเปรียบเทียบ |
| Slices | Array vs slice, make, append, copy |
| Maps | make(map[K]V), comma-ok, delete, การวน loop |
| Pointers | & และ *, mutation, nil pointer |
โปรแกรม composite type แรกของคุณ
หัวข้อที่มีชื่อว่า “โปรแกรม composite type แรกของคุณ”โปรแกรมด้านล่างนิยาม struct ชื่อ Point สร้าง slice []Point แล้วค้นหาชื่อจาก map[string]int ซึ่งแสดงให้เห็น value type (Point ถูก copy), slice literal, และการเข้าถึง map พร้อม zero-value เมื่อ key ไม่มีอยู่
package main
import "fmt"
type Point struct { X, Y int}
func main() { // Struct literal with named fields p := Point{X: 3, Y: 4} fmt.Println(p.X, p.Y)
// Slice of structs points := []Point{ {X: 0, Y: 0}, {X: 1, Y: 2}, {X: 3, Y: 4}, } for _, pt := range points { fmt.Printf("(%d, %d)\n", pt.X, pt.Y) }
// Map: string -> int score := map[string]int{ "Alice": 95, "Bob": 87, } fmt.Println(score["Alice"]) fmt.Println(score["Charlie"]) // missing key -> zero value (0)} ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| value type (struct, array) | predictable behavior, ไม่มี aliasing | copy cost สำหรับ type ขนาดใหญ่ |
| reference-like type (slice, map) | แชร์ data โดยไม่ต้อง copy | shared mutation — goroutine หนึ่งแก้ไขอาจกระทบอีกตัว |
| composite literals | readable initialization | syntax แตกต่างกันตาม type |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- array และ slice คือสิ่งเดียวกัน — array มีขนาดคงที่เป็นส่วนของ type เช่น
[3]int≠[4]int; slice เป็น dynamic view ของ array - map safe สำหรับ concurrent access — Go map ไม่ใช่ — ต้องใช้
sync.Mutexหรือsync.RWMutexถ้า goroutine หลายตัวอ่าน/เขียนพร้อมกัน