Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function Wrapper in GO

I need a function wrapper, which will take a function and return the wrapper version of it. What I try to achieve is inject some code before and after of the function's execution

func funcWrapper(myFunc interface{}){
    fmt.Println("Before")
    //call myFunc
    fmt.Println("After")
}
like image 385
platzmaPritzma Avatar asked Feb 16 '18 12:02

platzmaPritzma


People also ask

What is a wrapper in programming?

In the context of software engineering, a wrapper is defined as an entity that encapsulates and hides the underlying complexity of another entity by means of well-defined interfaces.

What is wrapper function in Python?

Wrappers around the functions are also knows as decorators which are a very powerful and useful tool in Python since it allows programmers to modify the behavior of function or class. Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it.


2 Answers

If you know the signature of the function, you can create a function that takes a function value of that function type, and returns another function value of the same type. You may use a function literal that does the extra functionality you want to add to it, and call the passed function when it's appropriate.

For example let's say we have this function:

func myfunc(i int) int {
    fmt.Println("myfunc called with", i)
    return i * 2
}

A function that takes an int and returns an int (double of its input number).

Here's a possible wrapper that "annotates" it with logging before and after calling it, also logging its input and return value:

func wrap(f func(i int) int) func(i int) int {
    return func(i int) (ret int) {
        fmt.Println("Before, i =", i)
        ret = f(i)
        fmt.Println("After, ret =", ret)
        return
    }
}

Example testing it:

wf := wrap(myfunc)
ret := wf(2)
fmt.Println("Returned:", ret)

Output (try it on the Go Playground):

Before, i = 2
myfunc called with 2
After, ret = 4
Returned: 4

Since Go does not support generics, you have to do this for each different function types you want to support. Or you may attempt to write a general solution using reflect.MakeFunc() as you can see in this question: Wrapper for arbitrary function in Go, it it's gonna be a pain to use it.

If you want to support multiple function types, best would be to create a separate wrapper for each distinct function type, so each can have the proper return type (function type with proper parameter and result types). It could look like this if you'd also want to support wrapping functions with no parameter and return types:

func wrap(f func()) func() {
    return func() {
        fmt.Println("Before func()")
        f2()
        fmt.Println("After func()")
    }
}

func wrapInt2Int(f func(i int) int) func(i int) int {
    return func(i int) (ret int) {
        fmt.Println("Before func(i int) (ret int), i =", i)
        ret = f(i)
        fmt.Println("After func(i int) (ret int), ret =", ret)
        return
    }
}

You may do it in a single wrap() function like below, but its cons (less type safety, harder to use) out-weight its pros, so I'd advise against it and I would just create separate wrapper functions for separate function types.

Let's also support wrapping a function with no parameter and return types:

func myfunc2() {
    fmt.Println("myfunc2 called")
}

The wrapper function:

func wrap(f interface{}) interface{} {
    switch f2 := f.(type) {
    case func(i int) (ret int):
        return func(i int) (ret int) {
            fmt.Println("Before func(i int) (ret int), i =", i)
            ret = f2(i)
            fmt.Println("After func(i int) (ret int), ret =", ret)
            return
        }
    case func():
        return func() {
            fmt.Println("Before func()")
            f2()
            fmt.Println("After func()")
        }
    }
    return nil
}

Testing it:

wf := wrap(myfunc).(func(int) int)
ret := wf(2)
fmt.Println("Returned:", ret)

wf2 := wrap(myfunc2).(func())
wf2()

Output (try this one on the Go Playground):

Before func(i int) (ret int), i = 2
myfunc called with 2
After func(i int) (ret int), ret = 4
Returned: 4
Before func()
myfunc2 called
After func()

Since there is no generics in Go, this solution can only have a return type interface{}, and when using it, its return value have to be manually "converted", type asserted to the function type you expect it to return (e.g. wf2 := wrap(myfunc2).(func())).

like image 142
icza Avatar answered Sep 20 '22 18:09

icza


Here’s one way to do it https://play.golang.org/p/ouzU2jiFDz2


package main

import (
    "fmt"
)

func trace(funcName string) func() {
    fmt.Println("pre", funcName)
    return func() {
        fmt.Println("post", funcName)
    }
}

func doSomething(name string) {
    defer trace("something")()
    fmt.Println(name)
}

func main() {
    doSomething("test")
}
like image 36
Arun Gopalpuri Avatar answered Sep 23 '22 18:09

Arun Gopalpuri