Michael Whatcott
Michael Whatcott

Reputation: 5965

How can I get a reference to a defer function?

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.
}

Upvotes: 5

Views: 253

Answers (1)

nemo
nemo

Reputation: 57639

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.

Upvotes: 11

Related Questions