hookenz
hookenz

Reputation: 38897

Function and argument unmarshalling in go?

Given an interface containing functions with varying parameters and a concrete implementation of that interface how do I take a stream of bytes and call methods from the interface based on that byte stream. Basically an RPC call.

e.g.

type funcs interface {
  func Hello(name string) (string, error)
  func Add(numbers...int) (result, error)
}

And lets say I had the following input data

Hello John
Add 1 2 3 4

I figure this has to be done with reflection but this package I find a bit of mystery to use. Not great examples. Looking at RPC packages is also a bit mysterious. I don't understand what they are doing and there seem to be limitations or assumptions, perhaps to make the code simpler and/or faster to implement.

Can someone shed some light on how I might take the string "Hello" and find the function handle "Hello" from the funcs table. And know that it's expecting a single parameter that is a string and then calling that function with the argument provided "John".

Upvotes: 1

Views: 321

Answers (1)

PaSTE
PaSTE

Reputation: 4548

You can do this using the reflect package (general caveats about performance and code readability apply). Just create a type and implement the functions for that type, then call reflect.MethodByName() on a Value created from an instance of that type:

package main

import (
    "fmt"
    "reflect"
)

type functions struct {}

func (f functions) Hello(name string) (string, error) {
    fmt.Println("Hello,", name)
    return name, nil
}

func main() {
    var f functions
    m := reflect.ValueOf(f).MethodByName("Hello")
    if m.IsValid() {
        // Either call f.Hello() here, or use the returned Value like so:
        v := make([]reflect.Value, 1) // function Values only take Value slices as arguments
        v[0] = reflect.ValueOf("Asha")
        r := m.Call(v)
        fmt.Println("Returned", r)
    }
}

This prints:

Hello, Asha Returned [Asha <error Value>]

(See https://play.golang.org/p/0q1i3WBuAL)

However, if you know the function names you want to be able to call a priori, or if you want the user to be able to use names other than the exact function name, just use a switch or if/else statement:

package main

import (
    "fmt"
    "strings"
)

func Hello(name string) {
    fmt.Println("Hello,", name)
}

func main() {
    userInput := make([]string, 2)
    userInput[0] = "HeLlO"
    userInput[1] = "재은"

    switch(strings.ToLower(userInput[0])) { // allow any case
    case "hello":
        Hello(userInput[1])
    default:
        panic("user input not recognized")
    }
}

This prints:

Hello, 재은

(See https://play.golang.org/p/oBxsI42q4a)

Upvotes: 1

Related Questions