I was looking into golang contexts recently, and found that the WithCancel()
is implemented in an interesting way.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
WithCancel()
returns a ctx, and also a func to cancel the very same context. Why is this done rather than introducing the .Cancel()
as a func of the type itself, like
func (c *cancelCtx) Cancel() {
c.cancel(true, Canceled)
}
I understand using a func return type allows you to run a different func depending on runtime conditions, but there's no dynamic here - it's always the same func. Is this just because for the functional paradigm?
Reference: https://cs.opensource.google/go/go/ /master:src/context/context.go;l=232-239?q=context&ss=go/go
CodePudding user response:
Not all contexts are cancel-able. You could argue that for those that aren't, the Cancel()
method could be a no-op.
But then you would always have to call Cancel()
whenever you work with context.Context
because you don't (can't) know whether it truly needs cancelling. This would be unnecessary in a lot of cases, would make code slower (cancel functions are usually called deferred) and would bloat the code.
Also, the power of cancelling a context is for its creator only. The creator may choose to share this responsibility by passing / sharing the cancel function, but if doesn't, sharing the context alone does not allow (should not allow) cancelling it. If Cancel()
would be part of context.Context
, this restriction could not be enforced. For details, see Cancel context from child.
Interfaces–especially those widely used–should be small and a minimum, not containing all, rarely useful things.