I cannot speak for anyone but myself, but I, personally, don’t use any of that criteria to determine if something is a function or a method. Looking through some of my personal projects, I seem to write methods instead of functions either
To implement an interface or
If the objects and their methods are the API.
Otherwise, I tend to write functions that accept interfaces.
I don’t really know how to describe #2 better, but I can give some examples:
Years ago, I made a logging package that was intended to work similarly to Python’s logging package. Loggers can have Handlers associated with them and Handlers have Formatters that format the Events that are sent to the Loggers.
There are a bunch of methods on the *Logger type such as Verbose, Info, Warn, etc. to log messages with corresponding severity levels. The Logger checks if the Event’s severity is greater than or equal to the Logger’s own minimum severity level and if so, dispatches the event to all of its Handlers, who in turn compare the Event’s severity Level their own severity Levels before formatting the Event with their Formatter and emitting it however they want.
My point in explaining all that is because the objects and their relationships are the API. Handlers are not an implementation detail of the Logger; they’re a key part of its API. Same with Formatters.
I did a similar thing with an argparse package and it has the same concept of objects being part of the API: First you create an ArgumentParser, you add Arguments to it and then you use the ArgumentParser to parse the arguments and get the resulting values.
Idiomatic Go code seems to try to avoid code like this. The more public types you have and the more public methods you have on those types makes it harder to refactor them, especially if your custom type has exported methods that return custom types themselves. For example, in that logging package I mentioned above, the purpose of a Formatter is to format an Event object into a string. This is a problem, for example, in structured logging where the desired logging output is a machine-readable, parseable, and queryable format, and not a human-readable string. By making Formatters a part of the public API, I’ve constrained the possible Handler implementations or I require them to expose dummy Formatter and SetFormatter methods.