Menu

Type assertion trong Golang: x.(T), comma-ok và type switch

Type assertion lấy giá trị cụ thể ra khỏi một interface. Tìm hiểu x.(T), dạng comma-ok không bao giờ panic, assert sang interface khác, type switch, và errors.As cho lỗi được bọc.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

x.(T): lấy giá trị ra khỏi interface

Một giá trị interface che đi kiểu cụ thể mà nó đang chứa. Type assertion x.(T) yêu cầu lấy lại kiểu đó.

Đầu ra:

gopher 6
0 false
string of length 6

x phải có kiểu interface. Assert trên một kiểu cụ thể sẽ không biên dịch được: invalid operation: s (variable of type string) is not an interface.

Dạng một giá trị sẽ panic

Khi assertion sai, dạng một giá trị panic với thông báo nêu tên cả hai kiểu:

Đầu ra:

recovered: interface conversion: interface {} is int, not string

Chỉ dùng dạng một giá trị khi một kiểu khác xuất hiện là lỗi lập trình mà bạn muốn chương trình dừng ngay. Mọi chỗ khác, hãy dùng comma-ok. Khi thất bại, comma-ok trả về giá trị zero của T và false.

Assert trên một interface nil cũng thất bại: var v any; v.(string) panic với interface conversion: interface {} is nil, not string, còn dạng comma-ok trả về "", false.

Assert sang một interface khác

T có thể là kiểu interface. Khi đó assertion thành công nếu giá trị động implement T, và kết quả vẫn giữ nguyên giá trị động đó. Thư viện chuẩn dùng cách này để kiểm tra các khả năng tùy chọn.

io.Copy kiểm tra xem nguồn có implement io.WriterTo không và dùng nó nếu có. fmt kiểm tra Stringer và error. Mẫu này cho phép một interface nhỏ giữ nguyên nhỏ gọn, trong khi nơi gọi vẫn hưởng lợi từ những cài đặt phong phú hơn.

Type switch

Khi một giá trị có thể là một trong nhiều kiểu, type switch thay cho cả chuỗi assertion. x.(type) chỉ hợp lệ bên trong một switch.

Đầu ra:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

Những quy tắc nên biết:

  • Trong case chỉ có một kiểu, x mang kiểu đó. Trong case liệt kê nhiều kiểu, và trong default, x mang kiểu của interface ban đầu (ở đây là any).
  • Các case được kiểm tra theo thứ tự. Đặt interface cụ thể hơn trước interface rộng hơn, vì một giá trị có thể thỏa mãn nhiều interface.
  • case nil chỉ khớp với interface nil, không khớp với interface đang chứa một con trỏ nil.
  • Type switch không có fallthrough.

errors.As: assertion cho lỗi được bọc

Lỗi thường được bọc thêm ngữ cảnh: fmt.Errorf("load config: %w", err). Type assertion trực tiếp chỉ nhìn vào lỗi ở lớp ngoài cùng và bỏ sót lỗi bên trong. errors.As đi dọc cả chuỗi.

Đầu ra:

type assertion finds it: false
errors.As finds it: open /no/such/file

Chỉ dùng type assertion với lỗi khi bạn chắc chắn lỗi không bị bọc, mà trên thực tế gần như không bao giờ. Trang lỗi tùy chỉnh trình bày errors.As, errors.Is và cách định nghĩa kiểu lỗi của riêng bạn.

Assertion, chuyển đổi kiểu hay generics

Bạn cóBạn muốnDùng
intfloat64chuyển đổi: float64(n)
any chứa một intlấy int đóassertion: v.(int)
any có thể là nhiều kiểurẽ nhánh theo kiểutype switch
một error có thể đã bị bọcmột kiểu lỗi cụ thểerrors.As
một hàm dùng được cho nhiều kiểuan toàn lúc biên dịchgenerics

Code đầy any và type switch thường là dấu hiệu cho thấy generics hoặc một interface đúng nghĩa sẽ diễn đạt ý định rõ hơn và bắt lỗi ngay lúc biên dịch.

Lỗi thường gặp

  • Dùng dạng gây panic với dữ liệu không đáng tin. JSON đã decode, giá trị map kiểu any, đầu vào từ plugin: luôn dùng comma-ok.
  • Assert sai kiểu số. Số trong JSON được decode vào any thành float64, nên v.(int) thất bại với chúng.
  • Assert kiểu giá trị khi bên trong là con trỏ. Nếu interface chứa *User thì v.(User) thất bại. Hãy assert v.(*User).
  • Dùng type assertion với lỗi. Hãy dùng errors.As.

Câu hỏi thường gặp

Type assertion trong Go là gì?

Là biểu thức x.(T), trong đó x là một giá trị interface. Nếu T là kiểu cụ thể, nó trả về giá trị chứa trong x dưới dạng T. Nếu T là kiểu interface, nó kiểm tra xem giá trị bên trong có cũng implement T không. Dạng một giá trị sẽ panic khi kiểm tra thất bại; dạng hai giá trị v, ok := x.(T) báo kết quả qua ok.

Kiểm tra kiểu của một giá trị interface trong Go như thế nào?

Dùng type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. Trong mỗi case, v mang kiểu của case đó. Nếu chỉ cần kiểm tra một kiểu, assertion comma-ok s, ok := x.(string) ngắn gọn hơn. fmt.Printf("%T", x) in ra tên kiểu để gỡ lỗi.

Type assertion và chuyển đổi kiểu trong Go khác nhau thế nào?

Chuyển đổi T(x) biến một giá trị kiểu này thành kiểu khác, như float64(n); trình biên dịch quyết định có được phép hay không, và nó không thể thất bại lúc chạy. Assertion x.(T) chỉ dùng được với giá trị interface và kiểm tra lúc chạy xem bên trong đang chứa kiểu gì. Bạn không thể viết int(x) cho một any đang chứa int; bạn cần x.(int).

Có nên dùng type assertion để kiểm tra kiểu lỗi không?

Không. Hãy dùng errors.As(err, &target). Assertion trực tiếp err.(*MyError) chỉ nhìn vào lỗi ở lớp ngoài cùng, nên nó thất bại ngay khi lỗi được bọc bằng fmt.Errorf("...: %w", err). errors.As thì đi dọc cả chuỗi lỗi được bọc.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu