Function call by name in Golang

The golang’s function is a code block like C’s, but it can also be assigned to a variable as its other types.

If you are not familiar with the function, Codewalk: First-Class Functions in Go should be a good starting point for you. Already known it? Let’s go on.

First of all, look at this PHP codes:

function foobar() {
    echo "Hello Golang\n";
}
$funcs = array(
    "foobar" => "foobar",
    "hello"  => "foobar",
);
$funcs["foobar"]();
$funcs["hello"]();

It will print:

mikespook@mikespook-laptop:~/Desktop$ php foobar.php 
Hello Golang
Hello Golang

It is very useful for calling a function with name matching.

So, is it possible to call a function by its name in Golang?
As a static, compiled programming language, the answer is No … and YES!

You can not do this in Golang:

func foobar() {
    // bla...bla...bla...
}
funcname := "foobar"
funcname()

You can do:

func foobar() {
    // bla...bla...bla...
}
funcs := map[string]func() {"foobar":foobar}
funcs["foobar"]()

But here’s a limitation that the map only work with the prototype “func()”, no input parameters and return arguments.
If you want to call some functions have different function’s prototypes, the interface{} should be used.

Yep! interface{}, like the void pointer in C. Remember that? No? Never mind! Read this: The Go Programming Language Specification:Interface types.

Then we could add functions with different prototypes into one map:

func foo() {
    // bla...bla...bla...
}
func bar(a, b, c int) {
    // bla...bla...bla...
}
funcs := map[string]interface{}{"foo":foo, "bar":bar}

How to call a function in the map? like this?

funcs["foo"]()

NO! It does not work! You can not call a function stored in a empty interface variable directly.
Dadadada…

Reflection comes to us! It is a package called “reflect” in Golang. Do you know reflection already?
If not, just read this: Laws of reflection.

func Call(m map[string]interface{}, name string, params ... interface{}) (result []reflect.Value, err error) {
    f = reflect.ValueOf(m[name])
    if len(params) != f.Type().NumIn() {
        err = errors.New("The number of params is not adapted.")
        return
    }
    in := make([]reflect.Value, len(params))
    for k, param := range params {
        in[k] = reflect.ValueOf(param)
    }
    result = f.Call(in)
    return
}
Call(funcs, "foo")
Call(funcs, "bar", 1, 2, 3)

Reflecting the function variable, use reflect.Call to call it and pass parameters into it at the same time.
Nothing could be hard to understand.

I’ve done a package for this functional: https://bitbucket.org/mikespook/golib/src/27c65cdf8a77/funcmap.

Hope this helps. Have a good time, gophers!

Comments

11 responses to “Function call by name in Golang”

  1. 四月份平民 Avatar
    四月份平民

    所有涉及到go的函数均应该为func关键字而非function:比如:
    function foo() {
    // bla…bla…bla…
    }
    function bar(a, b, c int) {
    // bla…bla…bla…
    }

  2. mikespook Avatar

    Fixed, thx!

  3. […] 在 Golang 中用名字调用函数  Golang  Add comments 七 122012   #leftcontainerBox { float:left; position: fixed; top: 60%; left: 70px; } #leftcontainerBox .buttons { float:left; clear:both; margin:4px 4px 4px 4px; padding-bottom:2px; } #bottomcontainerBox { width: 50%; padding-top: 1px; } #bottomcontainerBox .buttons { float: left; margin: 4px 4px 4px 4px; } 上个星期,我写了篇《Function call by name in Golang》。由于是英文的,所以被人诟病(说谁,谁知道!)。好吧,现在用中文重新写一遍。 […]

  4. Johan Avatar
    Johan

    How about:

    package mypackage

    func foo (s string, i int) (bool, error) {
    return false, nil
    }

    func bar (opts []string) (int, error) {
    return 456, nil
    }

    var Foo func(string, int) (bool, error)
    var Bar func([]string) (int, error)

    func init() {
    Foo = foo
    Bar = bar
    }

    func main() {
    b, err := Foo(“abc”, 345)
    i, err := Bar([]string{“abc”, “edf”})
    }

    You could use this for exporting private functions only for testing f.i..

    Johan

  5. Sandro Alvares Avatar
    Sandro Alvares

    Possible func variable value default?

    func foo (s string = “test”, i int = 1) (bool, error)

    How to do? its not work

  6. mikespook Avatar

    There is no default variable value for functions in Golang. If you want to do so, the `reflect` package may be the only choice for you. You can write a warp function which detects if the params are zero value, and then set a default value.

  7. Peter Avatar
    Peter

    Good article. I’m getting this error in your last example:
    cannot use funcs (type map[string]func()) as type map[string]interface {} in argument to Call

  8. […] should note that I have already “accomplished” this with something very similar to http://mikespook.com/2012/07/function-call-by-name-in-golang/ but require that each additional “plugin” add its exported function to a package global […]

  9. Ramil Avatar

    https://gist.github.com/yaxinr/679976f1cde2a7b2925c30fa89b03eea
    // Invoke – firstResult, err := invoke(AnyStructInterface, MethodName, Params…)
    func Invoke(any interface{}, name string, args …interface{}) (reflect.Value, error) {
    method := reflect.ValueOf(any).MethodByName(name)
    methodType := method.Type()
    numIn := methodType.NumIn()
    if numIn > len(args) {
    return reflect.ValueOf(nil), fmt.Errorf(“Method %s must have minimum %d params. Have %d”, name, numIn, len(args))
    }
    if numIn != len(args) && !methodType.IsVariadic(){
    return reflect.ValueOf(nil), fmt.Errorf(“Method %s must have %d params. Have %d”, name, numIn, len(args))
    }
    in := make([]reflect.Value, len(args))
    for i := 0; i = numIn-1 {
    inType = methodType.In(numIn – 1).Elem()
    } else {
    inType = methodType.In(i)
    }
    argValue := reflect.ValueOf(args[i])
    argType := argValue.Type()
    if argType.ConvertibleTo(inType) {
    in[i] = argValue.Convert(inType)
    } else {
    return reflect.ValueOf(nil), fmt.Errorf(“Method %s. Param[%d] must be %s. Have %s”, name, i, inType, argType)
    }
    }
    return method.Call(in)[0], nil
    }

  10. mikespook Avatar

    Thanks! This is the way to call methods of a struct by name.
    However, it won’t work with normal functions.
    BTW, the last return statement `method.Call(in)[0]`, the element length should be checked if equals 0 or not.

Leave a Reply

Your email address will not be published. Required fields are marked *