Eric
Eric

Reputation: 256

Dynamic function call in Go

I'm trying to dynamically call functions returning different types of struct.

For example, let's take the following code.

struct A {
   Name string
   Value  int
}

struct B {
   Name1 string
   Name2 string
   Value   float
}

func doA() (A) {
   // some code returning A
}

func doB() (B) {
   // some code returning B
}

I would like to pass either the function doA or doB as an argument to a generic function that would execute the function and JSON-encode the result. Like the following:

func Generic(w io.Writer, fn func() (interface {}) {
    result := fn()
    json.NewEncoder(w).Encode(result)
}

But when I do:

Generic(w, doA)

I get the following error:

cannot use doA (type func() (A)) as type func() (interface {})

Is there a way to achieve this dynamic call?

Upvotes: 7

Views: 25749

Answers (2)

Michael Laszlo
Michael Laszlo

Reputation: 12239

First, let me remark that func() (interface{}) means the same thing as func() interface{}, so I'll use the shorter form.

Passing a function of type func() interface{}

You can write a generic function that takes a func() interface{} argument as long as the function that you pass to it has type func() interface{}, like this:

type A struct {
    Name  string
    Value int
}

type B struct {
    Name1 string
    Name2 string
    Value float64
}

func doA() interface{} {
    return &A{"Cats", 10}
}

func doB() interface{} {
    return &B{"Cats", "Dogs", 10.0}
}

func Generic(w io.Writer, fn func() interface{}) {
    result := fn()
    json.NewEncoder(w).Encode(result)
}

You can try out this code in a live playground:

http://play.golang.org/p/JJeww9zNhE

Passing a function as an argument of type interface{}

If you want to write functions doA and doB that return concretely typed values, you can pass the chosen function as an argument of type interface{}. Then you can use the reflect package to make a func() interface{} at run-time:

func Generic(w io.Writer, f interface{}) {
    fnValue := reflect.ValueOf(f)        // Make a concrete value.
    arguments := []reflect.Value{}       // Make an empty argument list.
    fnResults := fnValue.Call(arguments) // Assume we have a function. Call it.
    result := fnResults[0].Interface()   // Get the first result as interface{}.
    json.NewEncoder(w).Encode(result)    // JSON-encode the result.
}

More concisely:

func Generic(w io.Writer, fn interface{}) {
    result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
    json.NewEncoder(w).Encode(result)
}

Complete program:

package main

import (
    "encoding/json"
    "io"
    "os"
    "reflect"
)

type A struct {
    Name  string
    Value int
}

type B struct {
    Name1 string
    Name2 string
    Value float64
}

func doA() *A {
    return &A{"Cats", 10}
}

func doB() *B {
    return &B{"Cats", "Dogs", 10.0}
}

func Generic(w io.Writer, fn interface{}) {
    result := reflect.ValueOf(fn).Call([]reflect.Value{})[0].Interface()
    json.NewEncoder(w).Encode(result)
}

func main() {
    Generic(os.Stdout, doA)
    Generic(os.Stdout, doB)
}

Live playground:

http://play.golang.org/p/9M5Gr2HDRN

Upvotes: 15

Guy Sirton
Guy Sirton

Reputation: 8401

Your return signature is different for these functions: fn func() (interface {}) vs. func doA() (A) and func doB() (B)

You are getting a compiler error because you are passing a function with a different signature into your Generic function. To address this issue you can change your functions to return interface{}.

This is an example of how to do that, I am using anonymous structs and printing the return value out rather than serializing them but this applies just the same to your example:

package main

import "fmt"

func doA() interface{} {
    return struct {
        Name  string
        Value int
    }{
        "something",
        5,
    }
}

func doB() interface{} {
    return struct {
        Name1 string
        Name2 string
        Value float64
    }{
        "something",
        "or other",
        5.3,
    }
}

func main() {
    fmt.Println("Hello, playground", doA(), doB())
}

Experiment with this in the Go Playground: http://play.golang.org/p/orrJw2XMW8

Upvotes: 0

Related Questions