I am trying to implement a set of functions in go. The context is an event server; I would like to prevent (or at least warn) adding the same handler more than once for an event.
I have read that maps are idiomatic to use as sets because of the ease of checking for membership:
if _, ok := set[item]; ok {
// don't add item
} else {
// do add item
}
I'm having some trouble with using this paradigm for functions though. Here is my first attempt:
// this is not the actual signature
type EventResponse func(args interface{})
type EventResponseSet map[*EventResponse]struct{}
func (ers EventResponseSet) Add(r EventResponse) {
if _, ok := ers[&r]; ok {
// warn here
return
}
ers[&r] = struct{}{}
}
func (ers EventResponseSet) Remove(r EventResponse) {
// if key is not there, doesn't matter
delete(ers, &r)
}
It is clear why this doesn't work: functions are not reference types in Go, though some people will tell you they are. I have proof, though we shouldn't need it since the language specification says that everything other than maps, slices, and pointers are passed by value.
Attempt 2:
func (ers EventResponseSet) Add(r *EventResponse) {
// ...
}
This has a couple of problems:
Any EventResponse has to be declared like
fn := func(args interface{}){}
because you can't address functions declared in the usual manner.You can't pass a closure at all.
Using a wrapper is not an option because any function passed to the wrapper will get a new address from the wrapper - no function will be uniquely identifiable by address, and all this careful planning is for nought.
Is it silly of me to not accept defining functions as variables as a solution? Is there another (good) solution?
To be clear, I accept that there are cases that I can't catch (closures), and that's fine. The use case that I envision is defining a bunch of handlers and being relatively safe that I won't accidentally add one to the same event twice, if that makes sense.
Which functions you mean to be equal? Comparability is not defined for functions types in language specification.
reflect.Value
gives you the desired behaviour more or lessthis assertion will treat as equal items produced by assignments only
but I don't think this behaviour guaranteed by any documentation.
You could use
reflect.Value
presented by Uvelichitel, or the function address as astring
acquired byfmt.Sprint()
or the address asuintptr
acquired byreflect.Value.Pointer()
(more in the answer How to compare 2 functions in Go?), but I recommend against it.Since the language spec does not allow to compare function values, nor does it allow to take their addresses, you have no guarantee that something that works at a time in your program will work always, including a specific run, and including different (future) Go compilers. I would not use it.
Since the spec is strict about this, this means compilers are allowed to generate code that would for example change the address of a function at runtime (e.g. unload an unused function, then load it again later if needed again). I don't know about such behavior currently, but this doesn't mean that a future Go compiler will not take advantage of such thing.
If you store a function address (in whatever format), that value does not count as keeping the function value anymore. And if no one else would "own" the function value anymore, the generated code (and the Go runtime) would be "free" to modify / relocate the function (and thus changing its address) – without violating the spec and Go's type safety. So you could not be rightfully angry at and blame the compiler, but only yourself.
If you want to check against reusing, you could work with interface values.
Let's say you need functions with signature:
Create an interface:
For example, you could have an unexported
struct
type, and a pointer to it could implement yourEventResponse
interface. Make an exported function to return the single value, so no new values may be created.E.g.:
Is it really needed to hide the implementation in a package, and only create and "publish" a single instance? Unfortunately yes, because else you could create other value like
&myEvtResp{}
which may be different pointers still having the sameDo()
method, but the interface wrapper values might not be equal:The type
*myEvtResp
implementsEventResponse
and so you can register a value of it (the only value, accessible viaGet()
). You can have a map of typemap[EventResponse]bool
in which you may store your registered handlers, the interface values as keys, andtrue
as values. Indexing a map with a key that is not in the map yields the zero value of the value type of the map. So if the value type of the map isbool
, indexing it with a non-existing key will result infalse
– telling it's not in the map. Indexing with an already registeredEventResponse
(an existing key) will result in the stored value –true
– telling it's in the map, it's already registered.You can simply check if one already been registered:
Closing: This may seem a little too much hassle just to avoid duplicated use. I agree, and I wouldn't go for it. But if you want to...