0

Normal use of function variables in Go allows them to be compared only to nil, not to one another. The reason for this (as it's been explained to me) is that, since Go has closures, the definition of equality is fuzzy. If I have two different closures with different values bound to local variables, but which use the same underlying function, should they be considered equal or unequal?

However, I do want to be able to make such a comparison. In particular, I have code like this (except, in my real code, the check is actually necessary - this is just a dummy example), where I compare a function pointer to a function literal:

func getFunc(which bool) (func ()) {
    if which {
        return func1
    } else {
        return func2
    }
}

func func1() { }
func func2() { }

f := getFunc(true)
if f == func1 {
    fmt.Println("func1")
} else {
    fmt.Println("func2")
}

Is there any way, for example using the reflect or unsafe packages, to get this to work?

joshlf
  • 21,822
  • 11
  • 69
  • 96
  • There is a reason why functions cannot be compared (you actually named it). While it might be possible to do with package reflect I cannot see any use in your example code above: If you do `f := getFunc(true)` you _know_ that you get func1. So why on earth compare? – Volker Aug 05 '13 at 20:34
  • 1
    That's not actually the code I'm using; it's just an example to give you a sense of the kind of comparison I'm trying to do (the `f == func1` part). – joshlf Aug 05 '13 at 20:36
  • Possible duplicate: http://stackoverflow.com/questions/9643205/how-do-i-compare-two-functions-for-pointer-equality-in-the-latest-go-weekly – BurntSushi5 Aug 06 '13 at 19:05

3 Answers3

4

You could compare the functions by name:

f := getFunc(true)
f1 := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
f2 := runtime.FuncForPC(reflect.ValueOf(func1).Pointer()).Name()
if f1 == f2 {
    fmt.Println("func1")
} else {
    fmt.Println("func2")
}

But this relies on both the reflect and runtime packages. Probably not a good idea to do this.

Do you really need to compare functions? I would consider an alternative if possible.

Luke
  • 13,678
  • 7
  • 45
  • 79
  • Yes, I definitely do. The use case is a bit oddly specific. It's for a proof-of-concept recursion-only rpn calculator: https://github.com/joshlf13/rpn – joshlf Aug 05 '13 at 20:53
  • @joshlf13 Interesting. Would it be possible to change `type operator func(int) operator` to `type operator func(int) (operator, int)`. Perhaps give yourself something extra to compare? – Luke Aug 05 '13 at 21:03
  • Yeah, I actually did that in an earlier version of the code, but I'm trying to keep it as clean as possible. I agree that it's totally doable, but I'd like to use /only/ recursion and continuation-passing if possible. – joshlf Aug 05 '13 at 21:04
  • @joshlf13 Passing an extra value is probably safer/faster anyway. `reflect` can be slow if used excessively, and I would caution using it with `runtime`. If you have a way of avoiding reflect, I would do it. – Luke Aug 05 '13 at 21:07
  • Performance really isn't a concern. After all, I'm using continuation-passing and creating closures instead of just using an explicit stack :). – joshlf Aug 05 '13 at 21:08
  • Btw, if you want to see an earlier (much uglier) version where I used special indicator values to indicate things like "zero": https://github.com/joshlf13/rpn/tree/1d34425a88811fcd5c040508213b0dc49e654aa1 – joshlf Aug 05 '13 at 21:08
  • @joshlf13 True! The other version doesn't look too bad though. Well, the answer I gave is the only way I found to compare a function. Hope that helps! – Luke Aug 05 '13 at 21:10
2

Extending upon @Luke's answer, it appears that I can directly test pointer equality. Note that this is really iffy. To quote the reflect.Value.Pointer() documentation:

If v's Kind is Func, the returned pointer is an underlying code pointer, but not necessarily enough to identify a single function uniquely. The only guarantee is that the result is zero if and only if v is a nil func Value.

That said, here's what you can do:

f := getFunc(true)
f1 := reflect.ValueOf(f).Pointer()
f2 := reflect.ValueOf(func1).Pointer()
eq := f1 == f2

Note that I did run a battery of tests (which I had used to regression-test the code that resulted from @Luke's answer) against this new version, and they all passed, which leads me to believe that the warning issued in the reflect documentation may be OK to ignore, but then, ignoring documentation is really never a good idea...

joshlf
  • 21,822
  • 11
  • 69
  • 96
  • These pointers can change, for example due to stack growth. But `runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()` is consistent and does not change. – vearutop May 09 '20 at 23:35
1

If all the functions you want to compare have the same signature, you could do something like this:

type cmpFunc struct {
    f func()
    id uint64
}

func (c *cmpFunc) call() { c.f() }
func (c *cmpFunc) equals(other *cmpFunc) { return c.id == other.id }

makeComparable(f func()) *cmpFunc {
    return &cmpFunc{f, get_uniq_id()}
}

Where get_uniq_id does what it says on the box. This gets a bit uglier because Go doesn't have () overloading, and it's more or less impossible without generics if you want to do this for functions in general. But this should work pretty well for your purposes.

  • Actually, this suggests another solution, which is to use types instead of functions altogether so that they can be compared directly. You basically do something like this: http://play.golang.org/p/WT3h3pIv1_ (that example is convoluted because of the setup, but it could be relatively simple in practice). The problem with that would be that it's not really pure continuation-passing (this is for [rpn](http://github.com/joshlf13/rpn), btw). – joshlf Aug 05 '13 at 22:00