Bài trước nói Go không có ép kiểu ngầm. Bài này về hệ quả thực tế của quy tắc đó, và ba chỗ người đến từ Java hay Python hay vấp.
Cắt cụt, không làm tròn
int(3.99) = 3
int(-3.99) = -3 <- cắt về phía 0, không phải làm tròn xuống
int8(300) = 44 <- cuộn vòng, không lỗi
Hai điều cần nhớ.
int(x) cắt phần thập phân, và cắt về phía 0 — nên int(-3.99) là -3 chứ không phải -4. Muốn làm tròn thì math.Round, muốn xuống thì math.Floor.
Chuyển sang kiểu hẹp hơn thì cuộn vòng im lặng. int8(300) cho 44 vì 300 vượt biên int8. Trình biên dịch không cản, go vet không cản. Đây là chỗ Go cố tình không bảo vệ bạn — cùng lý do với tràn số ở bài trước.
Khi đọc dữ liệu từ ngoài vào kiểu hẹp, hãy kiểm biên tường minh trước khi chuyển.
string(65) không phải "65"
Đây là cái bẫy tôi thấy người mới mắc nhiều nhất.
string(rune(65)) = "A"
Chuyển một số nguyên sang string trong Go nghĩa là "lấy ký tự Unicode có mã đó", không phải "viết số đó ra chữ". string(65) cho "A" vì 65 là mã của chữ A.
Từ Go 1.15, trình biên dịch cảnh báo khi bạn viết string(i) với i là int — nhưng nó vẫn biên dịch được. Với rune thì không cảnh báo, vì đó là cách dùng đúng.
Muốn đổi số thành chuỗi thì dùng strconv:
strconv.Itoa(65) // "65"
strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
fmt.Sprintf("%d", 65) // "65", chậm hơn nhưng linh hoạt
Và chiều ngược lại luôn trả kèm lỗi:
strconv.Atoi("42") = 42, err=<nil>
strconv.Atoi("42abc") -> err = strconv.Atoi: parsing "42abc": invalid syntax
Không có phiên bản "phân tích rồi ném exception" như Integer.parseInt của Java. Bạn buộc phải xử lý lỗi, và đó là chủ đề của bài 23.
Cái gì so sánh được bằng ==
struct so được : true
mảng so được : true
slice / map / func : LỖI BIÊN DỊCH
Go chia kiểu thành so sánh được và không so sánh được, và ranh giới không giống Java chút nào.
So sánh được: số, chuỗi, bool, con trỏ, channel, interface, mảng, và struct nếu mọi trường của nó so sánh được. Diem{1,2} == Diem{1,2} cho true — không cần viết equals, không cần hashCode. Đây là thứ Java phải chờ tới record mới có.
Không so sánh được: slice, map, function. Chúng chỉ so được với nil. Lý do: so sánh chúng hoặc rất tốn kém, hoặc không có định nghĩa rõ ràng.
Hệ quả quan trọng: struct chứa slice thì không dùng làm khoá map được, và trình biên dịch sẽ báo lỗi ngay. Muốn so sánh sâu thì reflect.DeepEqual, nhưng nó chậm và không nên nằm trong đường chạy nóng.
Vài chỗ khác của toán tử
Không có ++i, chỉ có i++, và nó là câu lệnh chứ không phải biểu thức. x := i++ là lỗi biên dịch.
Không có toán tử ba ngôi. Không có a ? b : c. Phải viết if. Đây là quyết định gây tranh cãi nhất của Go, và lý do chính thức là "biểu thức ba ngôi lồng nhau khó đọc".
% chỉ dùng cho số nguyên. Với float thì math.Mod.
Toán tử bit có thêm &^ — AND NOT, để xoá bit. Hiếm gặp ở ngôn ngữ khác.
&& và || vẫn đoản mạch như Java.
Thử ba mươi giây
fmt.Println(string(rune(9731)))
fmt.Println(strconv.Itoa(9731))
Dòng đầu in ra một hình người tuyết, dòng sau in ra 9731. Ba mươi giây để nhớ vĩnh viễn rằng string(số) và strconv.Itoa(số) là hai việc hoàn toàn khác nhau.
Ngày mai: if, switch và for — Go chỉ có một từ khoá cho mọi vòng lặp.