I just don't want to lock code into using the context package. The context setup is quite viral. For example, there is no simple way to construct a context from a quit channel (for some reason).
The mqtt package distinguishes between cancelation before and after submission. It is easy to flatten the error back into the context format. The explicitness may even help with understanding the consequences more clearly.
err := Publish(ctx.Done(), []byte("Hello"), "demo/+")
switch {
…
case errors.Is(err, mqtt.ErrCanceled), errors.Is(err, mqtt.ErrAbandoned):
return ctx.Err()
…
}