Menu

Toán tử ba ngôi Golang: vì sao Go không có và cách thay thế

Go không có toán tử ba ngôi (cond ? a : b). Trang này giải thích lý do, cách thay thế chuẩn bằng if/else, cmp.Or cho giá trị mặc định, và một hàm hỗ trợ generic cùng cái bẫy khiến nó khác với toán tử ba ngôi thật.

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.

Go không có toán tử ba ngôi (ternary operator). Không có cond ? a : b. Cách thay thế chuẩn là câu lệnh if:

Khai báo biến với giá trị mặc định, rồi đổi nó trong if. Khi cả hai nhánh đều cần tính toán, hãy dùng if/else:

var fee int
if member {
	fee = 0
} else {
	fee = computeFee(order)
}

Vì sao Go bỏ nó

FAQ của Go trả lời thẳng điều này: những người thiết kế đã thấy toán tử ?: bị dùng quá thường xuyên để tạo ra những biểu thức phức tạp khó hiểu, và quyết định rằng if/else, tuy dài hơn, rõ ràng hơn hẳn. Một ngôn ngữ chỉ cần một cấu trúc điều khiển rẽ nhánh có điều kiện.

Trên thực tế, thiếu toán tử này chỉ tốn thêm hai ba dòng mỗi lần dùng và loại bỏ cả một nhóm biểu thức lồng nhau như a ? b ? c : d : e.

Trả về sớm thay vì gán

Bên trong một hàm, cách gọn nhất thường bỏ hẳn biến:

Tách điều kiện ra thành một hàm nhỏ có tên thường là lựa chọn tốt nhất: chỗ gọi đọc lên giống toán tử ba ngôi bạn muốn, và tên hàm giải thích ý nghĩa của điều kiện.

Giá trị mặc định với cmp.Or (Go 1.22)

Toán tử ba ngôi phổ biến nhất trong các ngôn ngữ khác là "dùng giá trị này, hoặc giá trị dự phòng nếu nó rỗng": name ? name : "anonymous", hoặc name || "anonymous" trong JavaScript. Go 1.22 thêm cmp.Or đúng cho việc này. Hàm trả về đối số đầu tiên không phải giá trị zero của kiểu:

Hàm coi giá trị zero là "không có", nên không phân biệt được "port là 0 có chủ đích" với "port chưa được đặt". Khi 0 là một giá trị hợp lệ, hãy dùng con trỏ, một bool riêng, hoặc một if tường minh.

Hàm If generic và cái bẫy của nó

Với generics (Go 1.18), bạn có thể viết một hàm trông giống toán tử ba ngôi:

func If[T any](cond bool, a, b T) T {
	if cond {
		return a
	}
	return b
}

status := If(ok, "pass", "fail")

Hàm này dùng được với giá trị đơn giản, và một số codebase có dùng. Nhưng nó không phải toán tử ba ngôi, và khác biệt đó rất quan trọng. Go tính mọi đối số trước khi gọi hàm, nên cả a và b luôn được tính:

Lần gọi đầu tính cả "a" lẫn "b", và lần thứ hai gây panic do dereference con trỏ nil, dù điều kiện là false. Toán tử ba ngôi thật chỉ tính vế được chọn. Vì vậy:

  • Đừng bao giờ dùng hàm hỗ trợ như vậy khi một vế dereference con trỏ, truy cập slice theo chỉ số, hoặc đọc một khóa map có thể không tồn tại.
  • Đừng bao giờ dùng nó khi một vế tốn kém hoặc có tác dụng phụ.
  • Với giá trị rẻ và an toàn thì nó dùng được, nhưng nhiều người review code Go vẫn sẽ yêu cầu bạn viết if thông thường.

Bạn có thể làm các nhánh được tính lười bằng cách truyền hàm, If(ok, func() string { return a }, func() string { return b }), nhưng đến lúc đó câu lệnh if còn ngắn hơn.

Các mẹo khác và vì sao nên tránh

Tra cứu map. map[bool]string{true: "yes", false: "no"}[ok] chạy được, nhưng mỗi lần tính lại dựng một map và băm khóa, và tính cả hai giá trị. Đó là một trò lạ mắt, không phải cách viết chuẩn.

Hàm gọi ngay tại chỗ. Một hàm ẩn danh được gọi ngay cho bạn một biểu thức:

label := func() string {
	if n > 0 {
		return "positive"
	}
	return "non-positive"
}()

Cách này đúng là tính lười, và đôi khi có ích bên trong composite literal, nơi không đặt được câu lệnh. Phần lớn trường hợp, tính giá trị vào một biến ngay trước literal sẽ rõ ràng hơn.

Chọn cách thay thế

Tình huốngDùng
Chọn giữa hai giá trịgiá trị mặc định, rồi if
Cả hai nhánh đều cần tính toánif/else
Giá trị quyết định kết quả của hàmreturn sớm
Giá trị dự phòng khi giá trị rỗngcmp.Or(value, fallback)
Ba trường hợp trở lênswitch
Điều kiện lặp lại có ý nghĩa riêngmột hàm nhỏ có tên

Các đề xuất thêm biểu thức điều kiện xuất hiện đều đặn trên issue tracker của Go và cho đến nay đều bị từ chối. Hãy viết if.

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

Go có toán tử ba ngôi không?

Không. Go không có biểu thức cond ? a : b. FAQ của Go cho biết những người thiết kế cố ý bỏ nó vì nó quá hay bị dùng để viết những biểu thức khó đọc. Hãy dùng câu lệnh if/else.

Làm sao viết phép gán có điều kiện trên một dòng trong Go?

Gán giá trị mặc định trước, rồi ghi đè:

label := "odd"
if n%2 == 0 {
	label = "even"
}

Đó là cách thay thế chuẩn. gofmt sẽ không đặt thân if trên một dòng, nên cách này luôn cần ít nhất ba dòng.

Có viết được hàm ba ngôi generic trong Go không?

Được: func If[T any](cond bool, a, b T) T { if cond { return a }; return b }. Vấn đề là Go tính mọi đối số trước khi gọi hàm, nên cả a lẫn b đều được tính. If(p != nil, p.Name, "") vẫn dereference p đang nil và gây panic. Toán tử ba ngôi thật chỉ tính một vế.

Làm sao đặt giá trị mặc định khi chuỗi rỗng trong Go?

Từ Go 1.22, cmp.Or(name, "anonymous") trả về đối số đầu tiên không phải giá trị zero, nên kết quả là name trừ khi name là "". Hàm này dùng được với mọi kiểu so sánh được, và như mọi lời gọi hàm, nó tính tất cả đối số.

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