I don't think (c) "is" discouraged, so much as Dave Cheney doesn't like it. I think (a) and (b) are the best ways to do this, though.
Also: cards on the table: if there's no straightforward way to respond distinctively to different errors, I don't think error strings are really all that bad. I think it's reasonable to look at untyped errors as "this library function returns only one kind of error: it didn't work". A lot of legitimate interfaces are like that. Why spend extra effort typing their returns?
Because if in the next version of the library you decide that you want to return additional information along with the error, you will have to introduce a breaking change (if you use error strings; returning error interfaces or the equivalents in other languages should be safe I suppose).
Absolutely. If there is no reason to distinguish between different types of errors then defining different types is a pointless distraction that clutters the API.
So let's simply return errors.New("...") or a private package level error value.
But public package level singletons are a completely unnecessary burden on API versioning with no upside at all.
Why on earth would I ever want to make a lasting commitment to never attach any data or call specific message to errors in the future?
Also: cards on the table: if there's no straightforward way to respond distinctively to different errors, I don't think error strings are really all that bad. I think it's reasonable to look at untyped errors as "this library function returns only one kind of error: it didn't work". A lot of legitimate interfaces are like that. Why spend extra effort typing their returns?