英文:
Is there a sound use case for having multiple variables of the same name in different scopes?
问题
以下是翻译好的内容:
下面的(人为构造的)代码定义了变量i的两个实例或“出现”:
import "fmt"
func main() {
goto_done := false
i := 3
fred:
fmt.Printf("i #1 = %d\n", i)
if !goto_done {
i := 4
fmt.Printf("i #2 = %d\n", i)
goto_done = true
goto fred
}
}
从输出结果可以看出,这两个实例都作为独立的值存在,即定义第二个实例不会覆盖第一个实例:
i #1 = 3
i #2 = 4
i #1 = 3
这个特性有没有什么实际的用途,还是只是语言的一个怪癖?
英文:
The following (contrived) code defines two occurrences or "instances" of the variable i :
import "fmt"
func main() {
goto_done := false
i := 3
fred:
fmt.Printf("i #1 = %d\n", i)
if !goto_done {
i := 4
fmt.Printf("i #2 = %d\n", i)
goto_done = true
goto fred
}
}
and from the output, as follows, it can be seen that these both persist as separate values, in that defining the second does not overwrite the first:
i #1 = 3
i #2 = 4
i #1 = 3
Is there any constructive use case of this feature, or is it just a quirk of the language?
答案1
得分: 1
以下是几个用例:
1. 闭包
Go语言支持闭包,意味着你可以创建一个引用了外部变量的匿名函数。闭包有很多很好的用途,但这也意味着匿名函数会继承其外部变量的名称。
err := trySomething()
workChan := make(chan work)
// 启动一个工作线程
go func() {
// 我们在这里使用了外部作用域中的 workChan
for job := range workChan {
// vv 这是名称遮蔽!(我们遮蔽了外部的 err)
err := do(job)
if err != nil {
logError(err)
}
}
}()
err = tryAnotherThing()
if err != nil {
return err
}
如果我们不能遮蔽这个名称,那么工作线程和主线程将竞争使用同一个 err
变量(导致未定义的行为)。所以,你可以感谢遮蔽,因为你不必输入 err2
、errr
等等。
2. 在外部作用域中添加新名称
假设你有一个现有的包,并且你想要添加一个名为 gopher
的新全局常量或变量。你不想在整个包源代码中搜索每个作用域,看看是否已经在任何地方使用了名称 gopher
。
在 Go 1.18 版本中,添加了两个新的预声明标识符:any
和 comparable
。由于允许名称遮蔽,像这样的通用标识符可以被添加到Go语言中,而不会破坏先前正确的Go程序(还可以参见Go 1兼容性声明)。
3. 粘贴代码
遮蔽允许你复制和粘贴“自包含”的代码,然后在任何地方(几乎)都能正常工作。
// 打印斐波那契数列
for i, j := 0, 1; j < 100; i, j = j, i+j {
fmt.Println(j)
}
i
和 j
是非常常见的变量名,所以如果不允许遮蔽,如果你尝试将这段代码粘贴到你的代码中的某个地方,很有可能会导致冲突错误。
当然,通常最好避免遮蔽,因为它可能导致意外的错误,并且使你的代码不太易读(特别是在没有作用域感知高亮显示的IDE中)。在一个更长、更实际的场景中,你可能需要重命名任何已经遮蔽的名称。幸运的是,由于遮蔽在语言中是有效的,这实际上应该更容易重命名它们:
由于 i
和 j
是有效且不同的变量,一个好的IDE应该允许你快速重命名这些变量及其所有的使用,就像重命名任何其他变量一样。
英文:
Here are a few use cases:
1. Closures
Go has closures, meaning you can create an anonymous function which references enclosing variables. There are many great uses of closures, but this means that anonymous functions inherit their enclosing variable names.
err := trySomething()
workChan := make(chan work)
// start a worker
go func() {
// we're using the existing workChan from the enclosing scope
for job := range workChan {
// vv this is name shadowing! (we're shadowing the existing err)
err := do(job)
if err != nil {
logError(err)
}
}
}()
err = tryAnotherThing()
if err != nil {
return err
}
If we couldn't shadow the name, then the worker and the main thread would be competing for use of the same err
variable (resulting in undefined behaviour). So, you can thank shadowing for that you don't have to type err2
, errr
, etc.
2. Adding new names in enclosing scopes
Suppose you have an existing package and you want to add a new global constant or variable called gopher
. You don't want to have to search every scope in the the entire package source to see if the name gopher
was already used anywhere.
In the Go 1.18 release, there were added two new predeclared identifiers: any
and comparable
. Since name shadowing is allowed, universal identifiers like these can be added to Go without breaking previously correct Go programs (see also Go 1 compatability declaration)
3. Pasting code
Shadowing allows you to copy and paste "self contained" code like this, and have it work (pretty much) wherever you put it.
// Print Fibonacci numbers
for i, j := 0, 1; j < 100; i, j = j, i+j {
fmt.Println(j)
}
i
and j
are very common variable names so if shadowing isn't allowed, there's a good chance of conflict if you try to paste this somewhere in your code, resulting in an error.
Of course, it's generally good to avoid shadowing as it can lead to unexpected bugs and makes your code less readable (especially without scope-aware highlighting you might get in an IDE). In a longer, more realistic scenario, you would want to rename any shadowed names you might have pasted in. Thankfully, with shadowing being valid in the language, it should actually make it easier to rename them:
Since i
and j
are valid and distinct variables, a good IDE should allow you to quickly rename those variables and all their usages as you would any other variable.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论