Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get a reference to a defer function?

Tags:

go

This article states: "A defer statement pushes a function call onto a list." I'm wondering if I can access the elements in that list from another place in my program and then invoke them? Can I invoke them multiple times? I'm assuming that I have a reference to the function that has defer behavior (if that helps).

So, here's a short example of what I want to do:

func main {
    doStuff = func() {
        // open database connections
        // write temporary files
        // etc...

        defer func() {
            // close database connections
            // delete temporary files
            // etc...
        }()
    }

    AwesomeApplication(doStuff)
}

func AwesomeApplication(doStuff func()) {
    // Now, can I get a reference to the defer function within `doStuff`?
    // No, I can't just define the defer function somewhere an pass it
    // with `doStuff`.  Think of this as a curiosity I want to satisfy,
    // not a real use case.
}
like image 622
mdwhatcott Avatar asked Aug 26 '13 23:08

mdwhatcott


People also ask

How do you defer a function?

In Go language, defer statements delay the execution of the function or method or an anonymous method until the nearby functions returns. In other words, defer function or method call arguments evaluate instantly, but they don't execute until the nearby functions returns.

Does defer run before or after return?

defer statement is a convenient way to execute a piece of code before a function returns, as explained in Golang specification: Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.

Where do you put defer?

defer is often used where e.g. ensure and finally would be used in other languages. Suppose we wanted to create a file, write to it, and then close when we're done.

Does defer work with Panic?

In Go, we use defer, panic and recover statements to handle errors. We use defer to delay the execution of functions that might cause an error. The panic statement terminates the program immediately and recover is used to recover the message during panic.


1 Answers

The 'list' where to defer calls are stored are fully implementation specific so you have no reliable way of getting to this list.1,2 The implementation details for the *g compiler family (albeit a bit older) can be found in Russ Cox' research blog.

Deferred functions are associated with the current goroutine (g->Defer) and (in case of *g family) identified by the current stack pointer. If the current stack frame matches the stack frame stored in the topmost Defer entry, this function is called.

With this knowledge it is possible to access the list of deferred functions using cgo. You need to know

  • the current stack pointer
  • the address of the function
  • the current goroutine

However, I don't recommend using this. A general solution for the use case you're describing would be to have a function like this:

func setupRoutines() (setUp, tearDown func()) {
    // store db connection object and such

    return func() { /* connect db and such */ }, func() { /* close db and such */ }
}

In your code you could then share the tearDown function, which would be called using defer. This way you still have the bonus of having all your database connections and such local but you're able to share the initialization/disconnect functions.

Fiddle to play with

If you're really interested in playing around with unsafe and C, you can use the following code as a template.

inspect/runtime.c:

// +build gc
#include <runtime.h>

void ·FirstDeferred(void* foo) {
    foo = g->defer->fn;

    FLUSH(&foo);
}

inspect/inspect.go

package inspect

import "unsafe"

func FirstDeferred() unsafe.Pointer

defer.go

package main

import "defer/inspect"

func f(a, b int) {
    println("deferred f(", a, b, ")")
}

func main() {
    defer f(1, 2)
    println( inspect.FirstDeferred() )
}

This code (based on this) gives you access to the current go routine (g) and therefore the defer attribute of it. Therefore you should be able to access the pointer to the function and wrap it in a go FuncVal and return it.

like image 175
nemo Avatar answered Oct 10 '22 05:10

nemo