Go 我可以创建一个只能与“延迟”一起使用的函数吗?

Go 我可以创建一个只能与“延迟”一起使用的函数吗?,go,deferred,Go,Deferred,例如: package package // Dear user, CleanUp must only be used with defer: defer CleanUp() func CleanUp() { // some logic to check if call was deferred // do tear down } 在用户代码中: func main() { package.CleanUp() // PANIC, CleanUp must be def

例如:

package package

// Dear user, CleanUp must only be used with defer: defer CleanUp()
func CleanUp() {
    // some logic to check if call was deferred
    // do tear down
}
在用户代码中:

func main() {
    package.CleanUp() // PANIC, CleanUp must be deferred!
}
但如果用户运行以下命令,则一切都应该正常:

func main() {
   defer package.CleanUp() // good job, no panic
}

我已经尝试过的事情:

func DeferCleanUp() {
    defer func() { /* do tear down */ }()
    // But then I realized this was exactly the opposite of what I needed
    // user doesn't need to call defer CleanUp anymore but...
}
// now if the APi is misused it can cause problems too:
defer DeferCleanUp() // a defer inception xD, question remains.

好的,根据OPs请求,我发布了一个黑客方法,通过查看调用堆栈和应用一些启发式来解决这个问题

免责声明:不要在实际代码中使用此选项。我不认为延期付款是件好事。所以请不要投反对票:)

还要注意:只有当可执行文件和源文件在同一台机器上时,这种方法才有效

链接到要点:(这在操场上不起作用,因为你无法读取那里的源文件)


好的,根据OPs请求,我发布了一个黑客方法,通过查看调用堆栈和应用一些启发式来解决这个问题

免责声明:不要在实际代码中使用此选项。我不认为延期付款是件好事。所以请不要投反对票:)

还要注意:只有当可执行文件和源文件在同一台机器上时,这种方法才有效

链接到要点:(这在操场上不起作用,因为你无法读取那里的源文件)


即使您能够强制执行这样的操作,也有人可以创建一个函数,其唯一一行是
defer package.CleanUp()
,然后调用该函数,有效地调用您的函数而不使用
defer
(但实际上使用
defer
)。您的意思是
defer func(){defer CleanUp()}
?是的,圣诞老人讨厌这样的用户:)你能说更多你为什么要这么做吗?也许不同的方法可以达到相同的目标?@marcio——我认为没有
mutex.Unlock()
通常在对应的
mutex.Lock()
之后延迟,但它没有特殊的延迟状态。有时你只需要记录。我怀疑go阴谋集团会喜欢这个想法即使你能够强制执行这样的事情,也有人可以创建一个函数,其唯一行是一个
延迟包。CleanUp()
,然后调用该函数,有效地调用你的函数而无需
延迟
(但实际上是使用了一个
defer
)。你的意思是
defer func(){defer CleanUp()}
?是的,圣诞老人讨厌这种用户:)你能说更多你为什么要这样做吗?也许不同的方法可以达到相同的目标?@marcio——我认为没有
mutex.Unlock()
通常在对应的
mutex.Lock()
之后延迟,但它没有特殊的延迟状态。有时候你只需要记录一下。我怀疑围棋集团会喜欢这个想法。。。这是有史以来最令人讨厌的golang事件。我以为你会以某种方式访问延迟堆栈,而不是实际的源代码x)@marcio谢谢。如果您继续阅读
运行时
运行时/调试
源代码,它看起来有点像这样。他们把丑八怪藏在引擎盖下面。例如,这是为恐慌等打印堆栈的函数的来源:感谢ref。也许这个其他答案有助于改进此实现,也许可以访问延迟堆栈并查找fn调用。如果它不在那里,它就不会被推迟。@marcio哇,那太深了。考虑到运行时已被重写为1.4版,我想知道它是否仍然有效。但没有时间测试它,我有实际的代码要写:)谢谢,哇。。。这是有史以来最令人讨厌的golang事件。我以为你会以某种方式访问延迟堆栈,而不是实际的源代码x)@marcio谢谢。如果您继续阅读
运行时
运行时/调试
源代码,它看起来有点像这样。他们把丑八怪藏在引擎盖下面。例如,这是为恐慌等打印堆栈的函数的来源:感谢ref。也许这个其他答案有助于改进此实现,也许可以访问延迟堆栈并查找fn调用。如果它不在那里,它就不会被推迟。@marcio哇,那太深了。考虑到运行时已被重写为1.4版,我想知道它是否仍然有效。但是没有时间测试它,我有实际的代码要写:)
package main

import(
    "fmt"
    "runtime"
    "io/ioutil"
    "bytes"
    "strings"
)




func isDeferred() bool {

    // Let's get the caller's name first
    var caller string
    if fn, _, _, ok  := runtime.Caller(1); ok {
        caller = function(fn)
    } else {
        panic("No caller")
    }

    // Let's peek 2 levels above this - the first level is this function,
    // The second is CleanUp()
    // The one we want is who called CleanUp()
    if _, file, line, ok  := runtime.Caller(2); ok {

        // now we actually need to read the source file
        // This should be cached of course to avoid terrible performance
        // I copied this from runtime/debug, so it's a legitimate thing to do :)
        data, err := ioutil.ReadFile(file)
        if err != nil {
            panic("Could not read file")
        }

        // now let's read the exact line of the caller 
        lines := bytes.Split(data, []byte{'\n'})
        lineText := strings.TrimSpace(string(lines[line-1]))
        fmt.Printf("Line text: '%s'\n", lineText)


        // Now let's apply some ugly rules of thumb. This is the fragile part
        // It can be improved with regex or actual AST parsing, but dude...
        return lineText == "}" ||  // on simple defer this is what we get
               !strings.Contains(lineText, caller)  || // this handles the case of defer func() { CleanUp() }()
               strings.Contains(lineText, "defer ")


    } // not ok - means we were not clled from at least 3 levels deep

    return false
}

func CleanUp() {
    if !isDeferred() {
        panic("Not Deferred!")
    }


}

// This should not panic
func fine() {
    defer CleanUp() 

    fmt.Println("Fine!")
}


// this should not panic as well
func alsoFine() {
    defer func() { CleanUp() }()

    fmt.Println("Also Fine!")
}

// this should panic
func notFine() {
    CleanUp() 

    fmt.Println("Not Fine!")
}

// Taken from the std lib's runtime/debug:
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) string {
    fn := runtime.FuncForPC(pc)
    if fn == nil {
        return ""
    }
    name := fn.Name()
    if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
        name = name[lastslash+1:]
    }
    if period := strings.Index(name, "."); period >= 0 {
        name = name[period+1:]
    }
    name = strings.Replace(name, "·", ".", -1)
    return name
}

func main(){
    fine()
    alsoFine()
    notFine()
}