I accept a done
channel in my function, which is nice for notifying callers when some async process has finished, if they care. This is very useful for writing unit tests for example.
If my caller doesn't need this functionality, is there an idiomatic way to pass a garbage channel, where all values sent to it are discarded immediately? I was initially surprised sending nil
didn't work (sending to nil blocks the sender). I can think of one gross implementation (like a goroutine always running that consumes from this channel), but I would love to write something like:
func myFunc(foo int, done chan bool) {
....
}
func main() {
myfunc(4, _)
}
Is there a simple way to accomplish this?
You can use a select on the send to avoid blocking:
select {
case done <- true:
default:
}
The other alternative, and my preferred one, is to not send things over done channels. Instead, close the channel. This causes all blocked receive operations to instantly return, all at the same time (instead of having to send each one a value), and prevents blocking in the function sending the quit signal if nothing is listening. This also lets you replace the channel with a chan struct{}
, which is nice, because a struct{}
has 0 size.
Still, worth noting that closing a nil channel, instead of blocking, panics, so you still have to do a nil check on it.
Edit: my preferred style on this:
func myFunc(foo int, done chan<- struct{}) {
if done != nil {
defer close(done)
}
....
}
Edit 2: You could even make it a variadic, which allows the done channel to be omitted, or more than one to be provided.
func myFunc(foo int, done ...chan<- struct{}) {
for _, d := range done {
if d != nil {
defer close(d)
}
}
....
}
----
myFunc(1)
myFunc(2, ch1)
myFunc(3, ch2, ch3, ch4)
How about this:
func DoAllTheThings(done ...chan<- bool) {
// do the things
// all done!
if len(done) >= 1 {
done[0] <- true
}
return
}
If no channel is given in arguments, then nothing is sent. It would be up to the caller to set up a listener for the channel, obviously. Then you could call it as:
func main() {
DoAllTheThings()
}
or
func main() {
ch := make(chan bool)
go func() {
done := <- ch
if done {
fmt.Println("Yay!")
}
}()
DoAllTheThings(ch)
}