Type Assertions
Interface values ตอน runtime
หัวข้อที่มีชื่อว่า “Interface values ตอน runtime”เมื่อ value ถูกเก็บใน interface variable, Go เก็บสองสิ่งไว้ภายใน: pointer ไปยัง concrete type descriptor และ pointer ไปยัง concrete value type assertion ดึง concrete value กลับออกมา
Single-value form
หัวข้อที่มีชื่อว่า “Single-value form”var s Shape = Circle{Radius: 5}c := s.(Circle) // assert ว่า s เก็บ Circlefmt.Println(c.Radius)ถ้า s ไม่ได้เก็บ Circle นี่จะ panic ตอน runtime ใช้ form นี้เฉพาะเมื่อคุณแน่ใจใน dynamic type — เช่น ทันทีหลัง type check
Comma-ok form
หัวข้อที่มีชื่อว่า “Comma-ok form”variant ที่ปลอดภัยคืน boolean แทนการ panic:
c, ok := s.(Circle)if ok { fmt.Println("it's a circle with radius", c.Radius)} else { fmt.Println("not a circle")}ok เป็น true เมื่อ assertion สำเร็จ, false มิเช่นนั้น c เป็น zero value ของ Circle เมื่อ ok เป็น false ควรใช้ comma-ok form เสมอเมื่อ dynamic type ไม่แน่นอน
Type switch
หัวข้อที่มีชื่อว่า “Type switch”เมื่อต้องการ branch ตาม types หลายแบบ type switch สะอาดกว่าการเชื่อม if ok หลาย ๆ อัน:
switch v := x.(type) {case Circle: fmt.Println("circle", v.Radius)case Rect: fmt.Println("rect", v.W, v.H)default: fmt.Printf("unknown: %T\n", v)}ภายใน case แต่ละอัน v เป็น concrete type แล้ว — ไม่ต้อง assertion เพิ่มเติม default case ทำงานเมื่อไม่มี type ที่ระบุตรงกัน
Assert ไปยัง interface
หัวข้อที่มีชื่อว่า “Assert ไปยัง interface”คุณยัง assert จาก interface หนึ่งไปยังอีก interface ได้:
var w io.Writer = os.Stdoutif rc, ok := w.(io.ReadWriter); ok { // os.Stdout implements io.ReadWriter _ = rc}นี่คือวิธีที่โค้ด standard library ตรวจสอบ optional capabilities (เช่น ว่า Writer รองรับ Seek ด้วยหรือไม่)
package main
import "fmt"
type Animal interface { Sound() string}
type Dog struct{ Name string }type Cat struct{ Name string }
func (d Dog) Sound() string { return "Woof" }func (c Cat) Sound() string { return "Meow" }
func identify(a Animal) { // comma-ok form — safe if d, ok := a.(Dog); ok { fmt.Printf("Dog named %s says %s\n", d.Name, d.Sound()) return } // type switch for multiple cases switch v := a.(type) { case Cat: fmt.Printf("Cat named %s says %s\n", v.Name, v.Sound()) default: fmt.Printf("Unknown animal: %T\n", v) }}
func main() { animals := []Animal{Dog{Name: "Rex"}, Cat{Name: "Luna"}} for _, a := range animals { identify(a) }}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| type assertion v, ok := i.(T) | ปลอดภัย ไม่ panic | ต้องตรวจสอบ ok ทุกครั้ง |
| type assertion v := i.(T) | กระชับ | panic ถ้า dynamic type ไม่ตรง |
| type switch | handle หลาย type ได้ชัดเจน | verbose กว่า single assertion |
| reflect.TypeOf | introspect type ณ runtime | overhead สูง, ใช้เฉพาะจำเป็น |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- type assertion เสมอปลอดภัย — ไม่ใช้ comma-ok form จะ panic ทันทีถ้า type ไม่ตรง
- type switch เหมือน switch ทั่วไป — type switch ตรวจ dynamic type ของ interface ไม่ใช่ value
- type assertion = type conversion — conversion เปลี่ยน type representation, assertion ตรวจว่า interface เก็บ concrete type นั้นจริงหรือไม่
💡 ตัวอย่างจากของจริง
encoding/json: Unmarshal ใช้ type assertion เพื่อ handle
[]interface\{\}และmap[string]interface\{\}เมื่อ JSON structure ไม่รู้ล่วงหน้าnet/http: middleware ตรวจว่า ResponseWriter implement http.Flusher หรือ http.Hijacker ด้วย type assertion เพื่อ enable streaming หรือ WebSocket upgrade