Lesson 18 — Interface (3 module tương tác)

Ba minh hoạ dưới đây giúp bạn (1) trực tiếp nhìn quá trình "matching" giữa interface và type — bật/tắt method để xem type có satisfy không; (2) khám phá internals 2-word của interface value + pitfall "interface chứa typed nil"; (3) watch animation một type switch chạy qua từng case để dispatch theo dynamic type.

1. Implicit satisfaction matcher

Bên trái là một interface Writer với 1 method bắt buộc Write. Bên phải là 3 struct cụ thể. Hãy bật / tắt method của mỗi struct và quan sát Go có coi nó "satisfy Writer" hay không. Không có khai báo implements — Go check method set hoàn toàn implicit.

Interface yêu cầu:
Interface Writer
  • Write(p []byte) (int, error) REQUIRED
Các concrete types
Khái niệm: trong Go, type X satisfy interface I nếu method set của X chứa tất cả method của I. Có thêm method khác — OK, không sao (như FileWriter có cả Read/Close). Thiếu method nào — không satisfy.
⚠ Pitfall method set: nếu method có (p *T) receiver (pointer), thì chỉ *T satisfy interface, T (value) thì không. Module này không vẽ chi tiết đó — bạn xem §4 README.

2. Interface value internals — 2 words

Một biến interface trong Go gồm 2 words: con trỏ tới type descriptor và con trỏ tới value thật. Click các nút bên trái để gán biến với các giá trị khác nhau, xem 2 word thay đổi thế nào, và đặc biệt là khi nào i == nil.

interface i type word: nil data word: nil i == nil → true (true khi cả 2 word đều nil)
type word: nil
data word: nil
i == nil → true
Khi var i Stringer chưa gán gì, cả 2 word đều nil ⇒ i == niltrue. Đây là trường hợp duy nhất nil interface "thật".

3. Type switch dispatch — animate qua từng case

Một switch v := x.(type) chạy lần lượt qua các case, dừng ở case có type khớp với dynamic type của x. Hãy chọn một input và bấm Run để xem animation.

switch v := x.(type) {
case int:    fmt.Printf("int: %d\n", v*2)
case string: fmt.Printf("string len: %d\n", len(v))
case bool:   fmt.Printf("bool: %v\n", v)
case []any:  fmt.Printf("slice len: %d\n", len(v))
case map[string]any: fmt.Printf("map keys: %d\n", len(v))
case nil:    fmt.Println("nil")
default:     fmt.Printf("other: %T\n", v)
}
case int:pending
case string:pending
case bool:pending
case []any:pending
case map[string]any:pending
case nil:pending
default:pending
(chưa chạy)
Trong type switch, v được rebind theo từng case với type đã narrow xuống. Nhờ vậy bạn dùng v như đã có type cụ thể, không cần .(T) nữa. Đây là cách Go làm pattern matching theo type.