Golang中defer和闭包的深入解析与应用

Golang中defer和闭包的深入解析与应用 你好

我有以下代码。

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

我原本期望它返回一个非 nil 的错误。在 defer 中,我将错误设置为非 nil。但返回的却是 nil。我无法推断出为什么它是 nil。希望能得到一些见解。我肯定是误解了规范。

import (
    "fmt"
    "log"
)

func cleanUp() error {
    return fmt.Errorf("**ERROR: cleanUp() error")
}

func getMessageBug()(string,  error) {
    var err error
    s := "Ok"

    fmt.Println(&err, err)
    defer func() {
        err = cleanUp()
        s = "This too is buggy"
        fmt.Println(&err, err)
    }()

    return s, err
}

func main() {
    msg, err:= getMessageBug()
    if err != nil {
        log.Println(err)
    }
    log.Println(msg)
}

更多关于Golang中defer和闭包的深入解析与应用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

为了使返回值能在延迟函数中被修改,必须为返回值命名:https://play.golang.org/p/MdnuGzMM-UD

func main() {
    fmt.Println(f())
}

func f() (result string) {
    defer func() {
        result = "world"
    }()
    return "hello"
}

更多关于Golang中defer和闭包的深入解析与应用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个经典的Go语言defer与返回值捕获时机的问题。你的代码中,getMessageBug 函数在 return s, err 时,实际上已经将返回值(包括 err 的当前值,即 nil)保存到了函数的返回值存储区。随后,defer 函数修改的是局部变量 err,而不是已经确定的返回值。

让我们通过一个更清晰的示例来说明:

package main

import (
	"fmt"
)

func demonstrate() (result int) {
	defer func() {
		result = 42 // 直接修改命名返回值
	}()
	return 0 // 这里会将0赋给result,但defer会在return之后执行,最终返回42
}

func demonstrateBug() (int, error) {
	var err error
	defer func() {
		err = fmt.Errorf("defer error")
	}()
	return 100, err // 此时err为nil,返回值已经确定。defer修改的是局部变量err,不影响返回值
}

func demonstrateFixed() (result int, err error) {
	defer func() {
		err = fmt.Errorf("defer error") // 直接修改命名返回值err
	}()
	result = 100
	return // 等价于 return result, err
}

func main() {
	fmt.Println(demonstrate()) // 输出: 42

	val, err := demonstrateBug()
	fmt.Printf("Bug: val=%d, err=%v\n", val, err) // 输出: Bug: val=100, err=<nil>

	val, err = demonstrateFixed()
	fmt.Printf("Fixed: val=%d, err=%v\n", val, err) // 输出: Fixed: val=100, err=defer error
}

在你的原始代码中,getMessageBug 函数使用了非命名返回值。return s, err 这一行执行时,Go 会将 serr 的当前值复制到函数的返回值中。此时 errnil。随后,defer 函数执行,修改了局部变量 err,但为时已晚,返回值已经确定。

要修复这个问题,使 defer 能够修改返回的错误,你需要使用命名返回值:

func getMessageFixed() (s string, err error) {
    s = "Ok"
    defer func() {
        err = cleanUp()
        s = "This is fixed"
    }()
    return // 等价于 return s, err
}

在这个修复版本中,serr 都是命名返回值。return 语句(没有显式指定返回值)会返回 serr 的当前值。defer 函数直接修改了命名返回值 errs,因此修改会生效。

关键点在于:

  1. 对于非命名返回值,return 语句中的表达式会在 defer 函数执行前求值并复制到返回值存储区。
  2. 对于命名返回值,defer 函数可以直接修改它们,因为 defer 执行时,函数虽然已经 return,但还没有返回到调用方,命名返回值变量仍然在作用域内。

这就是为什么你的代码返回了 nil 错误,而使用命名返回值可以解决这个问题的原因。

回到顶部