当该资源超出周围功能的范围时,如何推迟资源清理?

How to defer resource cleanup when that resource outlives the scope of the surrounding function?

让我们以 this piece of code 为例,它使记录器写入本地文件而不是标准输出:

f, err := os.OpenFile("filename", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
        log.Fatal(err)
}   

defer f.Close()

log.SetOutput(f)

作者将此代码直接放入 main() 函数中,使其按预期工作。但是,如果我想将此代码放入一个专用函数中,然后 main() 可能会调用它,那么它将不再起作用,因为 f.Close() 将在记录器被使用之前被调用。

例如(如果上面的代码现在在一个名为 logToFile() 的函数中):

main() {
   logToFile()
   log.Print("I'm going to end up in stdout\n")
}

可以将它移到它自己的函数中,并且仍然按预期工作吗?

我在 opening/closing 的数据库连接中遇到过相同的情况。似乎唯一的方法是在 main() 中完成这两件事,但我认为如果我们可以将代码划分为函数,代码看起来会更清晰,也更像 SoC。这是 Go 中的禁忌吗?

您正在寻找这样的东西吗?

type closerFunc func() error

func logToFile(path string) closerFunc {
    f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        log.Fatal(err)
    }   

    log.SetOutput(f)

    return func() error {
        return f.Close()
    }
}

使用:

func main() {
    closerFn := logToFile("filename")
    defer closerFn()

    log.Print("logs to file\n")
}

一种选择是使用 continuation-passing style,将要在 defer 块中执行的代码作为显式参数传递:

func withLogToFile(filename string, body func()) {
    f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        log.Fatal(err)
    }
    prevOut := log.Writer()
    log.SetOutput(f)
    defer func() {
        log.SetOutput(prevOut)
        if err := f.Close(); err != nil {
            log.Fatal(err)
        }
    }()

    body()
}

那么调用站点变为:

func main() {
    withLogToFile(filename, func() {
        log.Print("I'm going to end up in ", filename)
    })
}

(https://play.golang.org/p/ebCvtzufU5U)