ข้ามไปยังเนื้อหา

Types & Data

ทุกการเรียก 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” ไม่ใช่กฎสี่ข้อที่ไม่เกี่ยวข้องกันที่ต้องท่องจำ

ทุกค่าใน 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 start

verify บน 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 ใน Go แบ่งเป็น value type (copy ตอน assign) และ reference-like type (copy แค่ header)
บทเรียนหัวข้อ
หน้านี้ภาพรวม Type และโปรแกรมตัวอย่างแรก
Structsการนิยาม struct, embedding, การเปรียบเทียบ
SlicesArray vs slice, make, append, copy
Mapsmake(map[K]V), comma-ok, delete, การวน loop
Pointers& และ *, mutation, nil pointer

โปรแกรมด้านล่างนิยาม 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, ไม่มี aliasingcopy cost สำหรับ type ขนาดใหญ่
reference-like type (slice, map)แชร์ data โดยไม่ต้อง copyshared mutation — goroutine หนึ่งแก้ไขอาจกระทบอีกตัว
composite literalsreadable initializationsyntax แตกต่างกันตาม type
  • array และ slice คือสิ่งเดียวกัน — array มีขนาดคงที่เป็นส่วนของ type เช่น [3]int ≠ [4]int; slice เป็น dynamic view ของ array
  • map safe สำหรับ concurrent access — Go map ไม่ใช่ — ต้องใช้ sync.Mutex หรือ sync.RWMutex ถ้า goroutine หลายตัวอ่าน/เขียนพร้อมกัน
อะไรคือ value type ใน Go — หมายความว่าการ assign จะ copy ข้อมูล?
Go จะ print อะไรเมื่อคุณอ่าน map key ที่ยังไม่เคยกำหนดค่า?
ทั้ง []Point slice และ [3]Point array เก็บค่า Point ความแตกต่างหลักคืออะไร?