Description
UPDATE: This proposal has shifted from the original description to the one described in comments below.
- errors: add ErrUnsupported #41198 (comment)
- errors: add ErrUnsupported #41198 (comment)
- errors: add ErrUnsupported #41198 (comment)
- errors: add ErrUnsupported #41198 (comment)
Go has developed a pattern in which certain interfaces permit their implementing types to provide optional methods. Those optional methods are used if available, and otherwise a generic mechanism is used.
For example:
io.WriteString
checks whether anio.Writer
has aWriteString
method, and either calls it or callsWrite
.io.Copy
checks the source for aWriterTo
method, and then checks the destination for aReaderFrom
method.net/http.(*timeoutWriter).Push
checks for aPush
method, and returnsErrNotSupported
if not found.
The io/fs proposal (#41190) proposes various other optional methods, such as ReadFile
, where there is again a generic implementation if the method is not defined.
The use of WriterTo
and ReaderFrom
by io.Copy
is awkward, because in some cases whether the implementation is available or not is only known at run time. For example, this happens for os.(*File).ReadFrom
, which uses the copy_file_range
system call which is only available in certain cases (see the error handling in https://golang.org/src/internal/poll/copy_file_range_linux.go). When os.(*File).ReadFrom
is called, but copy_file_range
is not available, the ReadFrom
method falls back to a generic form of io.Copy
. This loses the buffer used by io.CopyBuffer
, leading to release notes like https://golang.org/doc/go1.15#os, leading in turn to awkward code and, for people who don't read the release notes, occasional surprising performance loss.
The use of optional methods in the io/fs proposal seems likely to lead to awkwardness with fs middleware, which must provide optional methods to support higher performance, but must then fall back to generic implementations with the underlying fs does not provide the method.
For any given method, it is of course possible to add a result parameter indicating whether the method is supported. However, this doesn't help existing methods. And in any case there is already a result parameter we can use: the error
result.
I propose that we add a new value errors.ErrUnimplemented
whose purpose is for an optional method to indicate that although the method exists at compile time, it turns out to not be available at run time. This will provide a standard well-understood mechanism for optional methods to indicate that they are not available. Callers will explicitly check for the error and, if found, fall back to the generic syntax.
In normal use this error will not be returned to the program. That will only happen if the program calls one of these methods directly, which is not the common case.
I propose that the implementation be simply the equivalent of
var ErrUnimplemented = errors.New("unimplemented operation")
Adding this error is a simple change. The only goal is to provide a common agreed upon way for methods to indicate whether they are not available at run time.
Changing ReadFrom
and WriteTo
and similar methods to return ErrUnimplemented
in some cases will be a deeper change, as that could cause some programs that currently work to fail unexpectedly. I think the overall effect on the ecosystem would be beneficial, in avoiding problems like the one with io.CopyBuffer
, but I can see reasonable counter arguments.