Menu

Golang trả về nhiều giá trị: (value, error), named result

Hàm trong Go có thể trả về nhiều giá trị. Trang này trình bày cú pháp, quy ước (value, error), thành ngữ comma-ok, named result, naked return và định danh trống.

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.

Trả về nhiều hơn một giá trị

Liệt kê các kiểu kết quả trong ngoặc đơn. Trả về các giá trị cách nhau bằng dấu phẩy, và nhận chúng theo cùng cách.

Hàm có sẵn min và max có từ Go 1.21. Nơi gọi phải nhận mọi kết quả: x := minMax(nums) không biên dịch được, báo lỗi assignment mismatch: 1 variable but minMax returns 2 values.

Quy ước (value, error)

Lý do phổ biến nhất để trả về hai giá trị là báo lỗi. Theo quy ước, lỗi đứng cuối cùng, và khi nó khác nil thì các kết quả còn lại nên được coi là vô nghĩa.

Đầu ra:

port: 8080
error: port "http": strconv.Atoi: parsing "http": invalid syntax
error: port out of range

Khi thất bại, hãy trả về giá trị zero cho mọi kết quả khác (0, "", nil). Mẫu đầy đủ, gồm cả bọc lỗi và kiểm tra lỗi, có ở trang xử lý lỗi.

Thành ngữ comma-ok

Một số thao tác có sẵn trả về thêm một giá trị bool thứ hai (không bắt buộc nhận) cho biết giá trị đầu tiên có thật hay không. Theo quy ước, biến này được đặt tên là ok.

Ba dạng này (tra cứu map, type assertion, nhận từ channel) là trường hợp đặc biệt: chúng trả về một hoặc hai giá trị tùy vào số biến bạn gán. Hàm của bạn không làm được như vậy. Một hàm khai báo hai kết quả thì luôn trả về hai.

Định danh trống

_ nhận một giá trị rồi bỏ nó đi. Dùng nó cho những kết quả bạn không cần:

_, err := fmt.Sscan(input, &n)
key, _ := parseLine(line)

Bỏ qua lỗi bằng _ vẫn biên dịch được, nên chỉ làm vậy khi bạn biết lời gọi không thể thất bại hoặc thất bại cũng không sao. Ví dụ, strings.Builder.WriteString luôn trả về lỗi nil; còn đóng tệp sau khi ghi thì không như vậy.

Named result

Tham số kết quả có thể có tên. Chúng được khai báo ở đầu hàm, khởi tạo bằng giá trị zero, và một lệnh return không kèm giá trị sẽ trả về những gì chúng đang giữ.

Các tên này xuất hiện trong đầu ra của go doc, rất có ích khi hai kết quả cùng kiểu: (quotient, remainder int) nói lên nhiều hơn (int, int).

Một lệnh return trống trong hàm dài rất khó đọc, vì người đọc phải dò ngược lên để xem các kết quả đang giữ gì. Lời khuyên thường gặp: đặt tên kết quả khi tên đó có ý nghĩa tài liệu, và vẫn viết tường minh return quotient, remainder, trừ khi hàm chỉ dài vài dòng.

Named result và defer

Hàm được defer chạy sau khi câu lệnh return đã gán các kết quả và trước khi nơi gọi nhận được chúng. Với named result, hàm được defer có thể đọc và thay đổi chúng. Đây là cách bạn thêm ngữ cảnh cho mọi lỗi mà hàm trả về, hoặc biến một panic thành lỗi.

Chương trình in ra load : empty name rồi <nil>. return errors.New(...) trước hết gán vào err, sau đó hàm được defer bọc lỗi đó lại. Xem thêm ở defer.

Shadowing một named result

Named result nằm ở phạm vi ngoài cùng của hàm. Một phép := trong khối lệnh lồng nhau tạo ra biến mới trùng tên, và khi đó lệnh return trống trả về biến bên ngoài.

func find() (n int, err error) {
	if true {
		n, err := compute() // new n and err, shadowing the results
		_ = n
		_ = err
	}
	return // returns 0, nil
}

Trình biên dịch bắt được trường hợp tệ nhất: một lệnh return trống bên trong khối mà kết quả bị che sẽ báo lỗi result parameter err not in scope at return. Nó không bắt được phiên bản ở trên, nơi lệnh return nằm ngoài khối. Hãy dùng = thay cho := khi bạn muốn gán vào biến kết quả.

Lỗi thường gặp

  • Dùng giá trị khi err khác nil. Kiểm tra err trước. Một hàm hoàn toàn có thể trả về giá trị rác kèm theo lỗi.
  • Trả về con trỏ nil có kiểu dưới dạng error. var e *MyErr; return e trả về một error khác nil. Hãy trả về nil dạng literal khi thành công.
  • Quá nhiều kết quả. Ba kết quả trở lên với ý nghĩa khác nhau rất khó dùng. Hãy trả về một struct.

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

Làm sao trả về nhiều giá trị từ một hàm trong Go?

Liệt kê các kiểu kết quả trong ngoặc đơn và trả về các giá trị cách nhau bằng dấu phẩy: func minMax(xs []int) (int, int) { ...; return lo, hi }. Nơi gọi nhận tất cả cùng lúc: lo, hi := minMax(nums).

Làm sao bỏ qua một trong các giá trị trả về trong Go?

Gán nó cho định danh trống _: _, err := strconv.Atoi(s) hoặc n, _ := strconv.Atoi(s). Bạn không thể bỏ hẳn một giá trị: số biến ở vế trái phải bằng số kết quả.

Named return value trong Go là gì?

Là các tham số kết quả có tên, như func split(n int) (half, rest int). Chúng bắt đầu ở giá trị zero, có thể được gán trong thân hàm, và một lệnh return trống sẽ trả về giá trị hiện tại của chúng. Chúng cũng cho phép một hàm được defer thay đổi những gì nơi gọi nhận được.

Có nên dùng naked return trong Go không?

Chỉ trong hàm ngắn. Một lệnh return trống trong hàm dài che mất thứ đang được trả về và dễ sinh bug do shadowing. Các hướng dẫn phong cách Go khuyên đặt tên kết quả để làm tài liệu và trả về giá trị tường minh trong mọi hàm dài hơn vài dòng.

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