Alright, let’s get our hands dirty with the real party trick: calling methods and functions you only know by name at runtime. This is where reflect stops being a fancy mirror and becomes a full-on remote control for your code. It’s powerful, it’s a bit dangerous, and it feels like you’re breaking the rules, even though you’re not.

The core of this operation is the Value.Call method. But before you can Call anything, you need a reflect.Value representing the function itself. And crucially, that Value must be of Kind reflect.Func. If it’s not, you’re about to trigger a panic that will stop your program dead in its tracks. No gentle errors here; it’s the reflect package’s way of saying, “You should have known better.”

Let’s start with a simple, almost trivial example. We’ll call a function by name.

package main

import (
    "fmt"
    "reflect"
)

// A simple function to target.
func Greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

func main() {
    // Step 1: Get the function's Value from its name.
    funcValue := reflect.ValueOf(Greet)

    // Step 2: Verify it's actually a function. Good practice!
    if funcValue.Kind() != reflect.Func {
        panic("Hey, that's not a function!")
    }

    // Step 3: Prepare the arguments as a slice of reflect.Value.
    // This is the slightly tedious part.
    args := []reflect.Value{
        reflect.ValueOf("Gopher"),
    }

    // Step 4: Call it!
    funcValue.Call(args)
    // Output: Hello, Gopher!
}

Simple, right? The key thing to internalize is that Call expects a slice of reflect.Value, not a slice of interface{} or any other Go type. You must wrap every single argument in a reflect.Value.

Calling Methods on Structs

Now, the more common and useful scenario: calling a method on a struct dynamically. This involves a two-step dance: first, you get the Value of the struct instance, then you get the method by name from that Value.

type MyStruct struct {
    Name string
}

func (m *MyStruct) UpdateName(newName string) {
    m.Name = newName
    fmt.Printf("Name updated to: %s\n", m.Name)
}

func main() {
    myInstance := &MyStruct{Name: "OldName"}

    // Get the Value of the struct instance (a pointer, in this case).
    instanceValue := reflect.ValueOf(myInstance)

    // Get the method by its string name.
    method := instanceValue.MethodByName("UpdateName")

    // Check if it was found to avoid a nasty panic.
    if !method.IsValid() {
        panic("Method not found. Check your spelling!")
    }

    // Prepare the arguments and call it, just like before.
    args := []reflect.Value{reflect.ValueOf("NewDynamicName")}
    method.Call(args)
    // Output: Name updated to: NewDynamicName

    // Prove it worked on the original instance.
    fmt.Println(myInstance.Name) // Output: NewDynamicName
}

Notice we used MethodByName on the Value of the instance, not the type. This is why it works: the method is already bound to that specific instance. Also, note we used a pointer receiver (*MyStruct). If UpdateName was defined with a value receiver (MyStruct), reflect.ValueOf(myInstance) would still work, but reflect.ValueOf(*myInstance) would be the correct value to use for calling the value-receiver method. This is a classic gotcha. You’re essentially mimicking the same receiver type rules the Go compiler enforces at compile time, but you have to get it right manually at runtime.

Handling Return Values and Errors

Nothing ever just does something; useful functions return things. Value.Call() returns a slice of []reflect.Value, which you need to unwrap back into real Go types.

Let’s call a function that returns values and an error.

func Div(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    funcValue := reflect.ValueOf(Div)

    // Let's call it with a bad argument to trigger an error.
    args := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(0)}
    results := funcValue.Call(args)

    // The results are in a []reflect.Value. We know the signature,
    // so we can unpack them.
    intResult := results[0].Int() // gets the int value as int64
    errResult := results[1].Interface() // gets the interface{} value

    // Check if the error is nil. We have to do this the Go way.
    if errResult != nil {
        err := errResult.(error) // type assert it back to error
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", intResult)
    }
    // Output: Error: division by zero
}

The pattern is straightforward but verbose: call the function, get the slice of return Values, and then extract the concrete value from each using the appropriate method (.Int(), .String(), .Interface(), etc.). For errors, .Interface() and a type assertion is the standard way to go.

The Big Caveat: Performance and Safety

Let’s be brutally honest: you should not be doing this for every function call in your hot path. reflect.Call is orders of magnitude slower than a direct function call because of all the type checking and setup overhead at runtime. The compiler can’t optimize it. This is a tool for when the name of the function is dynamic input (e.g., from a configuration file, a network call, or a plugin system). If you know the function at compile time, just call it normally.

Furthermore, you lose all type safety. If you prepare an argument slice with the wrong number or types of arguments, you get a panic. There’s no compiler to catch you. Your test coverage must cover every dynamic call path.

So, use this power wisely. It’s the right solution for building flexible, plugin-based architectures or RPC systems, but it’s a terrible replacement for a simple switch statement. Remember, with great power comes great responsibility… and slightly slower execution.