18

I'm trying to use reflection in order to make a dynamic function call based on user input. I'm collecting user input like such:

func main() {
    for {
        reader := bufio.NewReader(os.Stdin)
        fmt.Print("> ")
        command, _ := reader.ReadString('\n')
        runCommandFromString(command)
    }
}

This is how the command is parsed out

func stringCommandParser(cmd string) *Command {
    cmdParts := strings.Split(
        strings.TrimSpace(cmd),
        " ",
    )
    return &Command{
        Name: cmdParts[0],
        Args: cmdParts[1:],
    }
}

func runCommandFromString(cmd string) {
    command := stringCommandParser(cmd)

    c := &Commander{}
    f := reflect.ValueOf(&c).MethodByName(command.Name)
    inputs := []reflect.Value{reflect.ValueOf(command)}
    f.Call(inputs)
}

The commands.go file looks like this

type Command struct {
    Name string
    Args []string
}

type Commander struct {}

func (c Commander) Hello(cmd Command) {
    fmt.Println("Meow", cmd.Args)
}

When I run the program, I get a prompt and I run a command called "Hello" with a param called "world". I would expect "Meow [World]" or something like that to appear. Like this:

> Hello world
Meow world

Instead what I get is a panic that looks like this:

> Hello world
panic: reflect: call of reflect.Value.Call on zero Value

goroutine 1 [running]:
panic(0x123360, 0xc420014360)
    /Users/parris/.gvm/gos/go1.7.4/src/runtime/panic.go:500 +0x1a1
reflect.flag.mustBe(0x0, 0x13)
    /Users/parris/.gvm/gos/go1.7.4/src/reflect/value.go:201 +0xae
reflect.Value.Call(0x0, 0x0, 0x0, 0xc420055e00, 0x1, 0x1, 0x0, 0x0, 0x0)
    /Users/parris/.gvm/gos/go1.7.4/src/reflect/value.go:300 +0x38
main.runCommandFromString(0xc42000c8a0, 0xc)
    /Users/parris/Projects/distro/main/utils.go:26 +0x1a0
main.main()
    /Users/parris/Projects/distro/main/main.go:14 +0x149
exit status 2

How can I fix this? Also, what is happening?

4 Answers 4

10

You might want to check what reflect.ValueOf returns before using it.

In this case you're getting a nil value because reflect can not see functions that aren't exported. hello starts with a lower case letter and therefore isn't exported.

Also, you're making too many pointers here, I suspect that you need to do reflect.ValueOf(c). You're doing and debugging too many things at the same time. Make a simple working example and continue debugging from there.

This works for me: https://play.golang.org/p/Yd-WDRzura

3
  • 1
    Updated my code. No luck by changing hello to Hello. Note: I also tried to directly call "Hello" by hardcoding it. I even tried replace the inputs variable like such: inputs := []reflect.Value{} and just had Hello not expect any params.
    – Parris
    Commented Feb 2, 2017 at 6:22
  • 1
    @Parris Updated my answer. The reason why people on this site ask for a simple, testable self contained examples is to avoid debugging too many issues at the same time. You probably have even more issues in the code. Can't really know because it's not testable.
    – Art
    Commented Feb 2, 2017 at 6:32
  • 3
    Soo the answer for me was "you're using pointers all over the place for stuff that doesn't want pointers". In this case, it was helpful to show the full example, because isolated things worked. I'm new to go, I don't know what I don't know.
    – Parris
    Commented Feb 2, 2017 at 15:24
8

Just do two changes in your code. First change reflect.ValueOf(&c) to reflect.ValueOf(c) Secondly change reflect.ValueOf(command) to reflect.ValueOf(*command)

This is the code workingcode

2

Call Kind() to check

type Header struct {
    Token string
}
    
type Request struct {
    Header 
}   


func requestedToken(request interface{}) string {
    requestValue := reflect.ValueOf(request)
    header := requestValue.FieldByName("Header12")
    if header.Kind() == 0 {
        return "" //Return here
    }
    token := header.FieldByName("Token")
    if token.Kind() == 0 {
        return ""
    }
    return token.String()
}
0

Just posting here (as an additional potential solving issue)

In my case, I had a nil value which was, after a .Elem(), resulting as a 0 value..

here is my scenario (simplified of course)

var myTime *time.Time
if v := reflect.ValueOf(myTime); v.Kind() == reflect.Ptr {
    if v.IsNil() {
        fmt.Println("it is nil")
    } else {
        // do anything else
    }
}

Without the "if" part, I was calling v.Elem().Interface() where Interface() was called on "zero" value. The IsNil() now prevents this and everything is working fine on my end

Hope it helps :)

Max

Not the answer you're looking for? Browse other questions tagged or ask your own question.