Menu

Golang 多返回值:(value, error) 与命名返回值

Go 函数可以返回多个值。本页讲解语法、(value, error) 约定、comma-ok 惯用法、命名返回值、裸返回以及空白标识符。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

返回多个值

在圆括号中列出返回值类型。返回时用逗号分隔各个值,接收时也一样。

内置的 min 和 max 从 Go 1.21 起就有了。调用者必须接收所有返回值:x := minMax(nums) 无法编译,会报错 assignment mismatch: 1 variable but minMax returns 2 values。

(value, error) 约定

返回两个值最常见的原因是报告失败。按照约定,错误放在最后;当它不为 nil 时,其他返回值都应视为没有意义。

输出:

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

失败时,其他所有返回值都返回零值(0、""、nil)。完整的模式,包括包装错误和检查错误,见错误处理页面。

comma-ok 惯用法

有几种内置操作会返回一个可选的第二个 bool 值,表示第一个值是否真实有效。这个变量按惯例命名为 ok。

这三种形式(map 查找、类型断言、通道接收)很特殊:它们返回一个值还是两个值,取决于你赋给几个变量。你自己的函数做不到这一点。声明了两个返回值的函数,总是返回两个值。

空白标识符

_ 接收一个值,然后把它丢掉。用它来接收你不需要的返回值:

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

用 _ 忽略错误能编译通过,所以只在你确定调用不会失败,或者失败也无所谓时才这样做。例如,strings.Builder.WriteString 总是返回 nil 错误;但写入后关闭文件就不是这样了。

命名返回值

返回值参数可以有名字。它们在函数开头声明,初始化为各自的零值,一个不带值的 return 会返回它们当前保存的值。

这些名字会出现在 go doc 的输出中,当两个返回值类型相同时尤其有用:(quotient, remainder int) 比 (int, int) 传达的信息更多。

在长函数中,裸 return 很难读,因为读者得往回翻才能知道返回值里装的是什么。通常的建议是:当名字能起到说明作用时就给返回值命名,但除非函数只有几行,否则仍然显式写出 return quotient, remainder。

命名返回值与 defer

defer 的函数在 return 语句设置好返回值之后、调用者收到它们之前运行。有了命名返回值,defer 的函数就可以读取和修改它们。借助这一点,你可以给函数返回的任何错误加上上下文,或者把 panic 转换成错误。

这会先打印 load : empty name,再打印 <nil>。return errors.New(...) 先把值赋给 err,然后 defer 的函数再包装它。更多内容见 defer。

遮蔽命名返回值

命名返回值位于函数的最外层作用域。在嵌套块中使用 := 会创建一个同名的新变量,而裸 return 返回的是外层那个。

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

编译器能发现最糟糕的情况:在返回值被遮蔽的块内写裸 return,会报错 result parameter err not in scope at return。但它发现不了上面这种 return 位于块外的写法。当你指的是返回值变量时,用 = 而不是 :=。

常见错误

  • 在 err 不为 nil 时使用返回的值。 先检查 err。函数在返回错误的同时,完全可以返回无意义的值。
  • 把带类型的 nil 指针作为 error 返回。 var e *MyErr; return e 返回的是一个非 nil 的 error。成功时请返回字面量 nil。
  • 返回值太多。 三个或更多含义各异的返回值很难使用。改为返回一个结构体。

常见问题

Go 语言的函数怎么返回多个值?

在圆括号中列出返回值类型,返回时用逗号分隔各个值:func minMax(xs []int) (int, int) { ...; return lo, hi }。调用者一次性接收所有返回值:lo, hi := minMax(nums)。

Go 语言怎么忽略其中一个返回值?

把它赋给空白标识符 _:_, err := strconv.Atoi(s) 或 n, _ := strconv.Atoi(s)。你不能完全省略某个值:左边的变量个数必须和返回值个数一致。

Go 中的命名返回值是什么?

就是带名字的返回值参数,比如 func split(n int) (half, rest int)。它们从各自的零值开始,可以在函数体中赋值,一个不带值的 return 会返回它们当前的值。它们还能让 defer 的函数修改调用者最终收到的结果。

Go 中应该使用裸返回吗?

只在短函数中使用。在长函数中,一个不带值的 return 会让人看不出返回的是什么,也容易引发变量遮蔽的 bug。Go 风格指南建议:为了文档作用可以给返回值命名,但只要函数超过几行,就显式写出返回的值。

Coddy 编程语言插图

和 Coddy 一起学编程

开始