Go 新提案:返回值应该明确使用或忽略?

大家好,我是煎鱼。

之前在写 Go 代码时 IDE 经常会提示,团队内 CodeReview 也会遇到一些方法的返回值,处理不处理的问题。一开始大家还会讨论一下,久而久之基本也就麻木了。

假期时翻资料学习时,看到了 Go 社区这个相关的 issues#20803。之前已经有大佬提过类似的疑惑,Go 团队也进行了回复。

官方算是给了一个初步的定论,今天分享给大家。和煎鱼一起学习!

快速背景

现在我们写 Go 程序时,如果函数或方法同时返回了返回值和错误参数,用户(程序员)必须要做出一些处理。

最经常的返回 error 参数的场景。如下代码:

go 复制代码
v, err := computeTheThing()

或是明确的忽略他。如下代码:

go 复制代码
v, _ := computeTheThing()

相信大家都这么干过。(没错,经常翻代码看到...)

但是在很多有唯一返回值(例如:io.Closer.Closeproto.Unmarshal)的场景下,写习惯后,有的就顺手忽略了。

最常见的代码:

go 复制代码
_ = json.Unmarshal(jsonBlob, &eddycjy)

又或是:

go 复制代码
defer fd.Close()

看了直呼好家伙...业务代码写的久的同学应该知道不可信任原则。我是经常遇到有人这么写,结果没有记错误信息,查半天没查到哪里出问题的。

即使不是错误参数的处理。在其他 API 中也有类似的问题。

如下代码:

go 复制代码
t := time.Now()
t.Add(10 * time.Second)  // Should be t = t.Add(...)

又或是:

go 复制代码
strconv.AppendQuote(dst, "eddycjy")  // Should be dst = strconv.AppendQuote(...)

总而言之,提案作者 @Bryan C. Mills 认为 "忘记" 进行错误检查或丢弃赋值的后果可能相当严重。

可能会出现如下问题:存储到生产数据库中的数据被破坏、用户数据无法提交到存储中、因未验证用户输入而导致程序崩溃等问题。

期望的解决方案

提案作者 @Bryan C. Mills 给出思路:建议 Go 默认拒绝未使用的返回值。

配合的解决方式是增加 ignore 内置关键字或其他方式来忽略任意数量的返回值。

如下 ignore 代码:

go 复制代码
go func() { ignore(fmt.Fprintln(os.Stderr, findTheBug())) }()

或是以下变形:

go 复制代码
go func() { ignore fmt.Fprintln(os.Stderr, findTheBug()) }()

又或是使用别的方式:

go 复制代码
go func() { _ fmt.Fprintln(os.Stderr, findTheBug()) }()

简而言之,就是较为显式的指定来忽略。

其他语言是如何处理的

  • Swift 会对未使用的返回值发出警告,但如果设置了 @discardableResult 属性,则允许抑制该警告。
    • 在 Swift 3 中进行修改之前,默认情况下可以忽略返回值(但可以使用 @warn_unused_result 添加警告)。
  • Rust 有 #[must_use] 属性。
  • C++17 有 [[nodiscard]] 属性,它将长期存在的 __attribute__((warn_unused_result)) GNU 扩展标准化。
  • ghc Haskell 编译器为未使用的结果提供了警告标志(-fwarn-unused-do-bind 和 -fwarn-wrong-do-bind)。
  • OCaml 默认会对未使用的返回值发出警告。它在标准库中提供了一个 ignore 函数。

提案作者认为 OCaml(这是一个函数式、指令式、模块化、面向对象的通用的编程语言)提供 ignore 函数的方式与 Go 最为贴合。

因此他提出的提案和方向也与此语言保持基本一致。

核心团队回复

Go 核心团队成员之一的老大哥 @Ian Lance Taylor 和 Go 创始人 @ Rob Pike,直接发了张好人卡给提案作者。(尴尬了...)

翻译过来的关键意思是:"我很同情大家的担忧,但你这个解决方案不够好"。

反对的原因是:

  • 降低代码可读性:我认为用 ignore 函数来包装表达式会掩盖代码的主要内容,从而增加代码的阅读难度。
  • 这个例子不够好:在一门重视简洁性的语言中,用 _, _ = 开头的典型例子 hello, world 在我看来是很不幸的。

结论是:"我同意这个问题,但不同意建议的解决方案"。(煎鱼注:高情商发言人?)

总结

通篇看下来,其实 Go 核心团队是较为认可这个问题的存在。但是既要也要还要的模式下,一时半会也找不到更好的解决思路。

同时许多社区内提出的解决思路都会一定程度的破坏 Go 现有的兼容性保障,让这件事变得前后都 "复杂" 了起来。

因此在后续中,Go 官方更推荐使用 vet 等 linter 工具检测来规避这个问题。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo... 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

推荐阅读

相关推荐
审判长烧鸡12 小时前
GO错误处理【6】显式哲学
go·显式哲学
审判长烧鸡13 小时前
GO错误处理【3】返回err与日志的结合
go·架构设计·报错处理
审判长烧鸡16 小时前
GO裸奔【1】动态SQL
go·动态sql·切片
审判长烧鸡1 天前
GO时区【2】跨时区应用
go·存储·时区
审判长烧鸡1 天前
Go结构体与指针【2】接收者应该怎么用
go·指针·结构体·接收者
王中阳Go1 天前
2026年了,还在纠结后端转AI要不要死磕Python?试试Go吧
后端·go·ai编程
审判长烧鸡1 天前
GO结构体与指针【1】什么时候用指针
go·指针·结构体
审判长烧鸡2 天前
GO错误处理【2】os.Exit(1)/panic/返回err的应用场景
go·异常处理·panic
审判长烧鸡2 天前
GO时区【4】PostgreSQL时区
postgresql·go
审判长烧鸡2 天前
GO时区【3】字段与连接设置
postgresql·go