Golang中短赋值语句为何看起来奇怪

Golang中短赋值语句为何看起来奇怪 在学习Golang的过程中,我接触到了短变量声明语句,但当它在源文件中被使用两次或更多次时,确实令人困惑。

// 更多代码
err := value
....
...
err : value
// 更多代码

运行这类代码后,我遇到了这种错误 -> /prog.go:10:4: no new variables on left side of :=

除此之外,我编写了一些通用的Golang代码,但对于上面提到的相同情况,它并没有抛出任何错误。

代码链接:https://play.golang.org/p/JKa0Q7vVwC4

package main

import (
	"fmt"
	"log"
)

func main() {

	value1, err := "VALUE1", "ERROR"

	if err != "ERROR" {
		log.Fatal(err)
	}

	fmt.Println(value1)

	value2, err := "VALUE2", "Error"

	if err != "Error" {
		log.Fatal(err)
	}

	fmt.Println(value2)
}

输出

VALUE1
VALUE2

Program exited.

在这段代码中,err 变量使用了2次 := 短变量声明语句。它确实在没有任何异常的情况下运行。

社区的朋友们,请向我解释一下这是如何发生的。


更多关于Golang中短赋值语句为何看起来奇怪的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

感谢 @hollowaykeanho ❤️

更多关于Golang中短赋值语句为何看起来奇怪的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢你 @petrus ❤️

你好 @hollowaykeanho,感谢你提供的有价值的更新,但我仍然有些困惑。

value1, err := "VALUE1", "ERROR"
....
value2, err := "VALUE2", "ERROR"

在源代码中,这两种写法都不会引发异常。它定义并声明了同一个变量两次。

https://play.golang.org/p/JKa0Q7vVwC4

还有更多

https://play.golang.org/p/xDD57i4Ohrs 这两个变量甚至拥有相同的地址。

ajilraju:

这两种情况在源代码中都不会导致异常。

它从来不需要,因为:

  1. 这不是对引用变量的赋值。
  2. value2 确实需要声明。如果我将其分解:
value1, err := "v1", "e1"           # value 1 = [声明并定义], err = [声明并定义]
...
value2, err := "v2", "e2"           # value 2 = [声明并定义], err = [定义]
...
value3, err := "v3", "e3"           # value 3 = [声明并定义], err = [定义]
...
value2, err = "v4", "e4"           #  value 2 = [定义], err = [定义]
...
value2, err := "v5", "e5"           # 错误:value 2 = [重新声明并定义], err = [重新声明并定义]

当涉及多个变量时,Go 接受这种简短赋值。你的第二个例子与我的解释一致(err 应该是同一个对象)。

value1, err := "VALUE1", "ERROR"
value2, err := "VALUE2", "Error"

你的第二个短赋值之所以能通过,是因为 value2 对于“声明并定义”来说确实是新的变量。Go 允许在存在新变量的情况下进行多次短赋值。

然而,上述规则不适用于引用中的赋值(例如结构体字段)。在这种情况下,你需要先声明 err 变量,然后再进行相应的赋值。例如,以下代码无法工作:

s := &MyStruct{A: 0}   // 某种结构体

s.A, err := somefunc(...)   // err 是新变量

以下代码可以工作:

var err error
s := &MyStruct{A: 0}   // 某种结构体

s.A, err = somefunc(...)

解释

短赋值仅仅是“声明并定义”的一种快捷方式,例如:

var err error                      // 声明 err
err = someFunc(...)                // 定义 err
err = anotherFunc(...)             // 定义 err

可以合并为一行:

err := someFunc(...)               // 声明并定义 err
err = anotherFunc(...)             // 定义 err

一般经验法则是,每个变量只能 声明 一次,并且在声明之后可以 定义 多次。因此,以下代码无法工作:

err := someFunc(...) # 声明并定义
err := anotherFunc(...) # 错误:重复声明并定义

请阅读Go编程语言规范

特别是短变量声明

与常规变量声明不同,短变量声明可以重新声明变量,前提是这些变量最初是在同一代码块(或者如果该块是函数体,则在参数列表中)以相同类型声明的,并且至少有一个非空白变量是新的。因此,重新声明只能出现在多变量的短声明中。重新声明不会引入新变量;它只是为原始变量分配一个新值。

运行以下程序:

package main

import (
    "fmt"
    "log"
)

func main() {
    value1, err := "VALUE1", "ERROR"
    fmt.Printf("%p %p\n", &value1, &err)
    if err != "ERROR" {
	    log.Fatal(err)
    }
    fmt.Println(value1, err)

    value2, err := "VALUE2", "Error"
    fmt.Printf("%p %p\n", &value2, &err)
    if err != "Error" {
	    log.Fatal(err)
    }
    fmt.Println(value2, err)
}

Go Playground

Go Playground - The Go Programming Language

0xc0000961e0 0xc0000961f0
VALUE1 ERROR
0xc000096220 0xc0000961f0
VALUE2 Error

输出结果符合预期。

短赋值语句(:=)在Go中的行为取决于左侧变量的声明状态。在你的第一个例子中,错误是因为在同一个作用域内重复声明同一个变量,而第二个例子能正常运行是因为它满足短变量声明的特殊规则。

关键规则:短变量声明(:=)要求左侧至少有一个变量是未声明的。当左侧变量都已存在时,:= 会退化为普通赋值。

示例分析

错误示例的原因

err := value1  // 第一次声明err
// ...
err := value2  // 错误:左侧没有新变量

这里第二次使用 := 时,err 已经存在,且左侧没有其他新变量,所以编译失败。

你的可运行代码分析

package main

import (
	"fmt"
	"log"
)

func main() {
	// 第一次声明:value1和err都是新变量
	value1, err := "VALUE1", "ERROR"

	if err != "ERROR" {
		log.Fatal(err)
	}

	fmt.Println(value1)

	// 第二次声明:value2是新变量,err已存在
	// 满足"至少有一个新变量"的规则,所以合法
	// err被重新赋值,value2被声明
	value2, err := "VALUE2", "Error"

	if err != "Error" {
		log.Fatal(err)
	}

	fmt.Println(value2)
}

更多示例

示例1:混合声明和赋值

package main

func main() {
	var x int
	y := 10      // y是新变量
	x, y := 5, 6 // x已存在,y已存在,但:=合法吗?
	// 错误:no new variables on left side of :=
}

示例2:至少一个新变量

package main

func main() {
	a := 1
	b := 2
	
	// 合法:c是新变量
	a, b, c := 3, 4, 5
	println(a, b, c) // 输出: 3 4 5
	
	// 合法:d是新变量
	b, c, d := 6, 7, 8
	println(b, c, d) // 输出: 6 7 8
}

示例3:作用域的影响

package main

func main() {
	x := 1
	
	if true {
		// 这是新的作用域,x是新变量(遮蔽外层的x)
		x, y := 2, 3
		println(x, y) // 输出: 2 3
	}
	
	println(x) // 输出: 1(外层x未改变)
}

示例4:函数返回值的处理

package main

func getValues() (int, error) {
	return 42, nil
}

func main() {
	// 第一次声明
	value, err := getValues()
	
	// 第二次:只有value是新变量
	value, err := getValues() // 错误:没有新变量
	
	// 正确写法:至少有一个新变量
	newValue, err := getValues() // 合法:newValue是新变量
	_ = newValue
}

实际应用场景

package main

import (
	"io"
	"os"
)

func processFile() error {
	// 第一次打开文件
	file1, err := os.Open("file1.txt")
	if err != nil {
		return err
	}
	defer file1.Close()
	
	// 处理file1...
	
	// 第二次打开文件 - 合法,因为file2是新变量
	file2, err := os.Open("file2.txt")
	if err != nil {
		return err
	}
	defer file2.Close()
	
	// 处理file2...
	
	return nil
}

func copyFile(src, dst string) error {
	// 打开源文件
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()
	
	// 创建目标文件 - 合法,dstFile是新变量
	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer dstFile.Close()
	
	// 复制文件 - 这里err被重新赋值
	_, err = io.Copy(dstFile, srcFile)
	return err
}

总结:短变量声明(:=)在左侧至少有一个新变量时是合法的。在你的可运行示例中,第二次使用 value2, err := ... 时,value2 是新变量,所以编译通过,err 被重新赋值。这是一个有用的特性,允许在需要声明新变量的同时重用错误变量。

回到顶部