0

I would like to pass a function pointer to a function to "anything".

It's easy to print something that gets passed in from just about anything (as in https://play.golang.org/p/gmOy6JWxGm0):

func printStuff(stuff interface{}) {
    fmt.Printf("Testing : %v", stuff)
}

Let's say, though, that I want to do this:

  1. Have multiple structs
  2. Have data loaded from various functions
  3. Have a generic print that calls the function for me

I tried this in a Play (https://play.golang.org/p/l3-OkL6tsMW) and I get the following errors:

./prog.go:35:12: cannot use getStuff1 (type func() SomeObject) as type FuncType in argument to printStuff

./prog.go:36:12: cannot use getStuff2 (type func() SomeOtherObject) as type FuncType in argument to printStuff

In case the Play stuff gets deleted, here's the code I'm trying to figure out how to get to work:

package main

import (
    "fmt"
)

type SomeObject struct {
    Value string
}

type SomeOtherObject struct {
        Value string
}

type FuncType func() interface{}

func getStuff1() SomeObject {
    return SomeObject{
        Value: "Hello, world!",
    }
}

func getStuff2() SomeOtherObject {
    return SomeOtherObject{
        Value: "Another, hello!",
    }
}

func printStuff(toCall FuncType) {
    stuff := toCall()
    fmt.Printf("Testing : %v", stuff)
}

func main() {
    printStuff(getStuff1)
    printStuff(getStuff2)
}

What is the secret sauce to get this stuff passed in properly?

Larger Goal Explanation

So what I am trying to accomplish here is reduction of boilerplate code that lives inside a gigantic file. Unfortunately I cannot refactor it further at this point due to other restrictions and I was wondering if this were possible at all considering the error messages and what I had read seemed to dictate otherwise.

There's a large amount of copy-and-paste code that looks like this:

func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
    loaded := contra.Load()
    err := trap.SnapBack(operation).send(loaded);

    // default error handling
    // logging
    // boilerplate post-process
}

func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
    loaded := policy.Load()
    err := trap.SnapBack(operation).send(loaded);

    // default error handling
    // logging
    // boilerplate post-process
}

// etc.

In these, the Load() functions all return a different struct type and they are used elsewhere throughout the application.

I want hoping to get something where I could have:

loaded := fn()
err := trap.SnapBack(operation).send(loaded);

// default error handling
// logging
// boilerplate post-process

Signature for send is, which accepts an interface{} argument:

func (s SnapBack) send(data interface{}) error
5
  • 2
    You can't. The only type that could hold both a func() SomeObject and a func() SomeOtherObject is interface{}. They are not assignable to func() interface{} because that is not their function signature. Commented Dec 18, 2019 at 18:00
  • 3
    The error message is pretty explicit and clear. type func() SomeObject and type func() interface{} are distinct, and therefore non-interchangeable. Either change your functions to return interface{}, or find another solution (another solution is probably best, since interface{} says nothing) Commented Dec 18, 2019 at 18:00
  • 2
    This smells a lot like an XY Problem. Can you explain your larger goal? Commented Dec 18, 2019 at 18:00
  • 2
    You also couldn't call them that way, so they'd be pretty useless. I would completely rethink this approach from the ground up to avoid the whole idea of passing around functions of different types. Commented Dec 18, 2019 at 18:01
  • @Flimzy I was trying to make some simplified calls to try and reduce boilerplate code that behaves similar to this but in a much larger context. I'll edit the question and add some larger scope. Commented Dec 18, 2019 at 18:02

1 Answer 1

-1

I don't know if you have control over the return values of contra.Load() and policy.Load(), for instance, so there may be a better approach, but assuming those cannot be modified, this would allow you to eliminate a lot of boilerplate, without any fancy manipulation:

func boilerplate(tram *TrapLapse, operation *TrapOperation, loader func() interface{}) {
    loaded := loader()
    err := trap.SnapBack(operation).send(loaded);

    // default error handling
    // logging
    // boilerplate post-process
}

func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
    boilerplate(trap, operation, func() interface{} { return contra.Load() })
}

func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
    boilerplate(trap, operation, func() interface{} { return policy.Load() })
}

If there's nothing more complex, you can also simplify this even further:

func boilerplate(tram *TrapLapse, operation *TrapOperation, loaded interface{}) {
    err := trap.SnapBack(operation).send(loaded);

    // default error handling
    // logging
    // boilerplate post-process
}

func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
    boilerplate(trap, operation, contra.Load())
}

func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
    boilerplate(trap, operation, policy.Load())
}
Sign up to request clarification or add additional context in comments.

4 Comments

That's perfect. I see what you did there - using the inline function to "cast/resend" the way it wants to be. Unfortunately I do not have control over those return values due to a hesitancy on a larger refactoring on the part of the lead architect on this particular project.
FYI - I also assumed I needed a type defined for that, but being able to use func() interface{} is much cleaner for my needs. So I learned something else now, too.
Even that is probably over-complex. I've provided a further simplification that I suspect will work just as well.
I see. That will work for this, but I have another where I need to account for an error being returned as well where the more complex configuration makes sense. The smaller version is what I was NOT going to do, but it seems like it's really the better option.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.