Go 语言怎么解决编译器错误“err is shadowed during return”?

简介: Go 语言怎么解决编译器错误“err is shadowed during return”?

01

介绍

在 Go 语言开发中,我们可能会遇到“错误在返回时被隐藏”的错误,该错误在 Go 编码时很难发现,在 GoLand 中也只是会变量名高亮提示,只有在编译 Go 项目时,Go 编译器会返回 err is shadowed during return

本文我们介绍为什么会出现该错误,以及我们应该怎么解决?

为什么出现该错误?

示例代码:

package main
import (
 "errors"
 "log"
)
func main() {
 err := foo()
 if err != nil {
  log.Printf("err=%v\n", err)
  return
 }
}
func foo() (err error) {
 if err := bar(); err != nil {
  return // Compiler reports: err is shadowed during return
 }
 return nil
}
func bar() error {
 err := errors.New("this is bar's err")
 return err
}

输出结果:

./main.go:18:3: err is shadowed during return

阅读上面这段代码,我们在编译代码时,编译器返回错误“err is shadowed during return”。

因为函数 func foo() (err error) 的返回值是具名参数,其作用域是函数 foo() 的函数体,在函数体中,if 分支使用短变量声明的方式重新声明变量 err,它的作用域是 if 分支。

if 分支声明的变量 err,它的内存地址与外层变量 err 不是同一个内存地址,而在 if 分支中使用 return 返回的是外层变量 err,所以 if 分支中的变量 err 被外层变量 err 所遮蔽,导致在编译 Go 项目时,Go 编译器返回错误“err is shadowed during return”。

03

怎么解决?

阅读完 Part02,读者朋友们已经了解了错误的原因。实际上,出现该错误,归根结底是我们没有真正掌握 Go 的基础知识。

为什么这么说呢?因为在我们公众号的历史文章中,关于 Go 变量声明、作用域、函数等基础知识都有介绍。

如果读者朋友们彻底掌握这些基础知识,大概率是不会遇到该错误“err is shadowed during return”。

解决该错误也比较简单,错误的原因是变量被遮蔽,我们通过使用不同的变量名,可以轻松规避这个错误。

示例代码:

...
func foo() (err error) {
 if err1 := bar(); err1 != nil {
  return
 }
 return nil
}
...

但是,使用不同的变量名真的解决问题了吗?

我们运行使用不同变量名的代码,确实 Go 编译器没有返回错误,我们可以正常编译 Go 项目。

细心的读者朋友们可能已经发现,该解决方案虽然可以规避 Go 编译器返回错误,但是并没有将错误传递到外层变量 err

所以,我们还需要将新变量 err1 的值赋值给外层变量 err,代码如下:

...
func foo() (err error) {
 if err1 := bar(); err1 != nil {
    err = err1
  return
 }
 return nil
}
...

现在,我们才算彻底解决了问题。改造后的代码,既不会引起 Go 编译器返回错误,也可以将错误信息传递出去。

读者朋友们如果有代码“洁癖”,肯定觉得这么写代码太不优雅了。那么,有没有优雅的解决方案呢?

答案是有更优雅的解决方案,我们在讲变量作用域的文章中也有讲过,在具名返回值的函数中,如果在函数体不同作用域中使用同名变量,不能直接返回,而是需要在 return 后面跟上变量名。

func foo() (err error) {
 if err := bar(); err != nil {
  return err
 }
 return nil
}

阅读上面这段代码,我们在 if 分支的作用域中,在 return 后面跟上变量名 err,该方式也可以解决问题,而且比使用不同变量名的方式更优雅。

现在,我们学会了两种解决方案。但是,还没有结束。我们示例代码中,调用函数 bar 是单返回值,在实际项目开发中,还会遇到调用函数是多返回值。

package main
import (
 "errors"
 "log"
)
func main() {
 err := foo()
 if err != nil {
  log.Printf("err=%v\n", err)
  return
 }
}
func foo() (err error) {
 if code, err := bar(); err != nil {
  log.Printf("code=%v err=%v\n", code, err)
  return // Compiler reports: err is shadowed during return
 }
 return nil
}
func bar() (int, error) {
 err := errors.New("this is bar's err")
 return 200, err
}

输出结果:

./main.go:19:3: err is shadowed during return

阅读上面这段代码,调用函数 bar() 是多返回值。

对于调用函数是多返回值的情况,除了我们已经讲的两种解决方式,还有其它解决方式。

...
func foo() (err error) {
  var code int
 if code, err = bar(); err != nil {
  log.Printf("code=%v err=%v\n", code, err)
  return
 }
 return nil
}
...

阅读上面这段代码,我们单独声明新变量 code,而不是使用短变量的方式声明新变量 code,避免变量 err 也被重新声明。

04

总结

本文我们介绍 Go 语言编译错误 err is shadowed during return 的原因和解决方案。先是介绍出现该错误的原因,然后介绍了解决该错误的三种解决方式。

需要注意的是,我们示例代码 foo 函数是具名返回值,本文讲的解决方案并不适用于匿名返回值的函数。

推荐阅读:

参考资料:

  1. https://groups.google.com/g/golang-nuts/c/HmmZXC7KcVw?pli=1
  2. https://go.dev/ref/spec#Short_variable_declarations
目录
相关文章
|
1天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
18 6
Go语言学习12-数据的使用
|
2天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
9 0
|
2天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
10 0
|
3天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
17 1
Go语言学习11-数据初始化
|
3天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
22 2
|
4天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
10 0
|
5天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
13 1
|
5天前
|
Go
|
6天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
134 1
|
6天前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
113 0