Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors: add ErrUnsupported #41198

Closed
ianlancetaylor opened this issue Sep 2, 2020 · 99 comments
Closed

errors: add ErrUnsupported #41198

ianlancetaylor opened this issue Sep 2, 2020 · 99 comments

Comments

@ianlancetaylor
Copy link
Contributor

ianlancetaylor commented Sep 2, 2020

UPDATE: This proposal has shifted from the original description to the one described in comments below.

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 an io.Writer has a WriteString method, and either calls it or calls Write.
  • io.Copy checks the source for a WriterTo method, and then checks the destination for a ReaderFrom method.
  • net/http.(*timeoutWriter).Push checks for a Push method, and returns ErrNotSupported 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.

@ianlancetaylor ianlancetaylor added this to the Proposal milestone Sep 2, 2020
@darkfeline
Copy link
Contributor

darkfeline commented Sep 3, 2020

This seems to overlap with the existing way of testing whether a method/interface is supported:

foo, ok := bar.(Foo) // Does bar support Foo?

Will we end up in situations where callers need to do two checks for whether a method is supported (the type assertion and an ErrUnimplemented check)? I know we can forbid that by convention, but it seems like a really easy trap to fall into.

@ianlancetaylor
Copy link
Contributor Author

This new error is intended to be used with type assertions. Type assertions test availablity at compile time, and the error tests availablity at run time. Something like io.copyBuffer would change to look like this:

	if wt, ok := src.(WriterTo); ok {
		written, err := wt.WriteTo(dst)
		if err != errors.ErrUnimplemented {
			return written, err
		}
		// WriteTo existed but was not actually implemented, carry on as though it did not exist.
	}

In other words, yes, for these cases you are expected to write two tests.

@darkfeline
Copy link
Contributor

darkfeline commented Sep 3, 2020

I see, this is about the implementation determining whether the method can be provided at runtime time, rather than the caller determining whether the "dynamic type" of a value implements the method at runtime. I see the value for this for existing code, but new code can use an interface type with a different implementation determined at runtime.

type Foo interface {}
type Bar interface {}
func NewFoo() Foo {
  if runtimeCheck() {
    return specialFoo{} // supports .(Bar)
  } else {
    return basicFoo{}
  }
}

Does this proposal include recommendations for the wider community on whether to use ErrUnimplemented or the above for new code?

@josharian
Copy link
Contributor

Can we spell it without the Err prefix, since it is in the errors package?

@tooolbox
Copy link

tooolbox commented Sep 3, 2020

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.

Would it suffice for io/fs to define an ErrNotImplemented sentinel? It's not clear to me that it's a positive change to broadly sanction the concept of "methods that don't actually work at runtime".

@narqo
Copy link
Contributor

narqo commented Sep 3, 2020

(A minor suggestion) I feel Unimplemented is hard to read. Can I suggest

errros.NotImplemented

@mvdan
Copy link
Member

mvdan commented Sep 3, 2020

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.

Has any research gone into whether this can be solved at compile time, not needing to add extra run time checks which Go programs would need to add? I know it's a hard problem to solve, but I assume it's worth a try before falling back to run time checks.

@rsc
Copy link
Contributor

rsc commented Sep 3, 2020

An early version of the FS interface draft had such an error, which is maybe part of what inspired this proposal.
I removed it from the FS interface draft before publishing, because in almost all cases I could think of, it was better for a method to either (1) report a definitive failure, or (2) fall back to code that does implement the behavior.

The fact that io.Copy checks for two optional methods complicates a lot and may well have been a mistake. I would rather not make general conclusions about the awkwardness of io.Copy.

Let's look instead at fs.ReadFile, which you raised as an example. Suppose I have an fs wrapper type that adds a prefix to all the underlying calls:

type Subdir struct {
    prefix string
    fsys fs.FS
}

If that type wants to make the ReadFile method on fs available, it can already do:

func (s *Subdir) ReadFile(file string) ([]byte, error) {
    file = path.Join(s.prefix, file)
    if fsys, ok := s.fsys.(fs.ReadFileFS); ok {
        return fsys.ReadFile(file)
    }
    return fs.ReadFile(fsys, file)
}

With this proposal, the code could instead do:

func (s *Subdir) ReadFile(file string) ([]byte, error) {
    file = path.Join(s.prefix, file)
    if fsys, ok := s.fsys.(fs.ReadFileFS); ok {
        return fsys.ReadFile(file)
    }
    return errors.ErrUnimplemented
}

The last line is the only one that changed.

Compared to the first version, being able to write the second version is only slightly less work for the wrapper author, but far more work for the call sites. Now every time a method like this gets called, the caller must check for ErrUnimplemented and do something else to retry the operation a different way. That is, ErrUnimplemented is a new kind of error for Go programs, a "it didn't quite fail, you have to do more work!" error.

And it's not the case that you only have to worry about this if you've tested for an optional interface right before the call. Suppose you have code that takes a value of the concrete type *Subdir as an argument. You can see from godoc etc that there's a ReadFile method, 100% guaranteed. But now every time you call ReadFile you have to check for ErrUnimplemented.

The pattern of "handle the call one way or another" seems much better for more code than the pattern of "refuse to handle the call". It preserves the property that when an error happens, it's a real failure and not something that needs retrying.

In that sense, ErrUnimplemented is a bit like EINTR. I'm wary of introducing that as a new pattern.

@rsc
Copy link
Contributor

rsc commented Sep 3, 2020

On a much more minor point, given that package errors today exports four symbols, none of which has type error, I don't believe that "this is an error" is implied by errors.. (For example, errors.Unwrap is not an error about unwrapping.)

If we are at some point to add one or more standard error values to package errors, the names should probably continue the convention of using an Err prefix. That will be avoid readers needing to memory which symbols in package errors are and are not errors.

@rsc
Copy link
Contributor

rsc commented Sep 3, 2020

For the record, although it's mostly unrelated to this discussion, io.CopyBuffer was a mistake and should not have been added. It introduced new API for a performance optimization that could have been achieved without the new API.

The goal was to reuse a buffer across multiple Copy operations, as in:

buf := make([]byte, size)
for _, op := range ops {
    io.CopyBuffer(op.dst, op.src, buf)
}

But this could instead be done using:

w := bufio.NewWriterSize(nil, size)
for _, op := range ops {
    w.Reset(op.dst)
    io.Copy(w, op.src)
    w.Flush()
}

There was no need to add CopyBuffer to get a buffer reused across Copy operations. Nothing to be done about it now, but given that the entire API was a mistake I am not too worried about the leakiness of the abstraction.

Credit to @bcmills for helping me understand this.

@rhysh
Copy link
Contributor

rhysh commented Sep 3, 2020

The result of an optional interface check is always the same for a particular value. Code can branch based off of that and know that the value won't suddenly implement or un-implement the optional method. (Consider an http.Handler that uses http.Flusher several times per response.)

What are the rules for methods that return ErrUnimplemented? If a method call on a value returns it, does that method need to return it on every subsequent call? If a method call on a value doesn't return it (maybe does a successful operation, maybe returns a different error), is the method allowed to return it on a future call?

If there were a way to construct types with methods at runtime (possibly by trimming the method set of an existing type), with static answers for "does it support this optional feature", would that address the need?

@tooolbox
Copy link

tooolbox commented Sep 3, 2020

Has any research gone into whether this can be solved at compile time, not needing to add extra run time checks which Go programs would need to add? I know it's a hard problem to solve, but I assume it's worth a try before falling back to run time checks.

This is where my mind goes on this subject. It seems like the conclusion has been made that this isn't possible, but I think the space is worth exploring.

If there were a way to construct types with methods at runtime (possibly by trimming the method set of an existing type), with static answers for "does it support this optional feature", would that address the need?

I suppose if you had interface A with method set X, and wanted to wrap it with B with method set Y (superset of X) you could re-wrap it with A afterwards to ensure that the final result had only the method set of X. Then at compile time you're assured to not call methods of B which are not actually supported by the underlying A.

@tv42
Copy link

tv42 commented Sep 4, 2020

Can we please name it ErrNotImplemented? We have os.ErrNotExist not os.ErrNonexistent,

What are the rules for methods that return ErrUnimplemented? If a method call on a value returns it, does that method need to return it on every subsequent call?

Example where guaranteeing that would be tough: A filesystem that delegates to other filesystems based on a prefix of the path. Any FS-level optional interface that takes path names can lead to different implementations. Forcing the multiplexer to cope with "one answer must stick", either way, could get messy.

@tv42
Copy link

tv42 commented Sep 4, 2020

@rsc

The pattern of "handle the call one way or another" seems much better for more code than the pattern of "refuse to handle the call". It preserves the property that when an error happens, it's a real failure and not something that needs retrying.

That's fine for cases where the optional interface is an optimization that can be safely ignored. That's not always true, though. Consider cp --reflink=always on a file tree larger than my free disk; if I can't copy-on-write it, I want to abort.

@jimmyfrasche
Copy link
Member

Another pattern could be a HasX() bool method that returns true if the wrapped object actually implements method X.

If there's no HasX method but there is a method X, you assume that X is supported.

This is a bit heavy but not much more than a sentinel error check.

It can be ignored when it doesn't matter and old code will continue to function. If it matters, new code can query it and make a decision about how to proceed. This also let's multiple methods on multiple objects be queried before anything happens.

It also has the nice property that you can see in the documentation that an optimization may or may not be available.

@jimmyfrasche
Copy link
Member

I wrote out a simple example for io.StringWriter even though it's probably overkill for something that simple.

It's a bit wordy to have an optional interface for an optional interface but seems to work fine and is easy enough to use.

https://play.golang.org/p/WCnA9tCk189

@tv42
Copy link

tv42 commented Sep 4, 2020

@jimmyfrasche

Another pattern could be a HasX() bool method that returns true if the wrapped object actually implements method X.

An FS delegating to other FS'es based on path prefix can't answer that yes-or-no without knowing the arguments to the call (the path to delegate based on).

@jimmyfrasche
Copy link
Member

Would it work if the HasX methods for the FS took the path as an argument?

@tv42
Copy link

tv42 commented Sep 4, 2020

Maybe, but you're still left with a TOCTOU race. Consider the delegation mapping changing on the fly.

@tooolbox
Copy link

tooolbox commented Sep 4, 2020

@tv42 sounds like you're arguing in favor of ErrNotImplemented, correct?

@tv42
Copy link

tv42 commented Sep 4, 2020

@tooolbox I'm trying to discover requirements and make sure people realize their consequences. So far, I haven't seen anything else cover everything. That's not quite the same as having fixed my take on a winner, new ideas welcome!

@bcmills
Copy link
Contributor

bcmills commented Sep 8, 2020

See previously #39436, but that is proposed for operations that are not supported at all, not as an indication to fall back to an alternate implementation.

@rsc
Copy link
Contributor

rsc commented Sep 11, 2020

@tv42

That's fine for cases where the optional interface is an optimization that can be safely ignored. That's not always true, though. Consider cp --reflink=always on a file tree larger than my free disk; if I can't copy-on-write it, I want to abort.

You lost me a bit here.

If the method in question is defined to do exactly X, it should definitely not do almost-X instead. I think that's true in general, with or without ErrUnimplemented.

Translating to your example (I hope!), suppose there's a Copy method and an optional CopyReflink method, and CopyReflink is considered a reasonable optimized implementation of Copy, but Copy is not a valid implementation of CopyReflink.

Then I would expect that func Copy might look for a CopyReflink method, use it if possible, and otherwise fall back to Copy.
But of course a CopyReflink implementation would never settle for calling Copy instead. In this case, I think you'd write:

func Copy(x Copier, src, dst string) error {
    if x, ok := x.(CopyReflinker); ok {
        if err := x.CopyReflink(src, dst); err == nil {
            return nil
        }
    }
    return x.Copy(src, dst)
}

As written, if x.CopyReflink fails for any reason, Copy falls back to plain x.Copy. In this case, that seems fine: if it fails, presumably no state has changed so doing x.Copy is OK.

But regardless of whether I got the above right, having ErrUnimplemented available doesn't seem to help any. If x.CopyReflink returns ErrUnimplemented, then we agree that the code would fall back to x.Copy. But what if it returns a different error, like "cannot reflink across file systems"? Shouldn't that fall back to x.Copy too? And what if it returns "permission denied"? Maybe that shouldn't fall back to x.Copy, but presumably x.Copy will get the same answer.

In this case there are other errors that should be treated the same way as ErrUnimplemented, but not all. So testing for ErrUnimplemented introduces a special path that is either too special or not special enough.

@tv42
Copy link

tv42 commented Sep 11, 2020

@rsc You've successfully debated against my point wrt cp --reflink=always. I think part of the confusion comes from GNU making the various modes of cp --reflink= all be called cp. CopyReflink makes sense, and behaves as cp --reflink=always.

I think this sort of "try an optimization" codepaths should only fall back to the unoptimized case on specific errors explicitly talking about the optimization (here, e.g. EXDEV), not just any error. And in that world, ErrUnimplemented is one of those specific errors. I will happily admit this is more of a philosophical stance than something I can vigorously defend. I don't like programs "hitting the same failure multiple times". I don't like seeing e.g. multiple consecutive attempts to open the same file, with the same error, just because the code tries all possible alternatives on errors that aren't specific to the alternate codepath.

@ianlancetaylor
Copy link
Contributor Author

Consider a file system operation like os.Link, which creates a new hard link to an existing file. Not all file systems support hard links. A middleware file system might want to provide the Link method. But it might turn out that the target file system does not provide the Link method. There is no fallback operation for the middleware file system: if hard links aren't supported, there is no substitute. What error should the middleware Link method return if the target file system does not define Link?

Of course we can define a particular error result for Link that means "hard links not supported," but it seems like a moderately general concept. (I suppose one could also make an argument for returning syscall.EMLINK, which means "file already has maximum number of hard links", but that doesn't seem entirely satisfactory.)

@bcmills
Copy link
Contributor

bcmills commented Sep 16, 2020

@ianlancetaylor, the Link case is exactly why I proposed os.ErrNotSupported (#39436). (One of my motivating examples there was file locking, which is similar: either the filesystem supports locking or it doesn't, and no semantically-equivalent fallback is possible.)

I still prefer the ErrNotSupported naming over ErrUnimplemented. To me, “unimplemented” suggests that the functionality can and should exist and the author just hasn't gotten around to it, while “not supported” does not carry any implication about whether the unsupported operation could, in principle, become supported.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/475857 mentions this issue: syscall: handle errors.ErrUnsupported in isNotSupported

gopherbot pushed a commit that referenced this issue Mar 15, 2023
Updates #41198

Change-Id: Ifed913f6088b77abc7a21d2a79168a20799f9d0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/475857
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/476578 mentions this issue: syscall: let EPLAN0 and EWINDOWS implement errors.ErrUnsupported

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/476578 mentions this issue: syscall: let EPLAN9 and EWINDOWS implement errors.ErrUnsupported

gopherbot pushed a commit that referenced this issue Mar 15, 2023
As suggested by Bryan. This should fix the failing
TestIPConnSpecificMethods on plan9 after CL 476217 was submitted.

For #41198

Change-Id: I18e87b3aa7c9f7d48a1bd9c2819340acd1d2ca4e
Reviewed-on: https://go-review.googlesource.com/c/go/+/476578
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/476875 mentions this issue: syscall: let ENOSYS and ENOTSUP implement errors.ErrUnsupported

gopherbot pushed a commit that referenced this issue Mar 16, 2023
…ported

As suggested by Bryan, also update (Errno).Is on windows to include the
missing oserror cases that are covered on other platforms.

Quoting Bryan:
> Windows syscalls don't actually return those errors, but the dummy Errno
> constants defined on Windows should still have the same meaning as on
> Unix.

Updates #41198

Change-Id: I15441abde4a7ebaa3c6518262c052530cd2add4b
Reviewed-on: https://go-review.googlesource.com/c/go/+/476875
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/476916 mentions this issue: syscall: let errors.ErrUnsupported match ERROR_NOT_SUPPORTED and ERROR_CALL_NOT_IMPLEMENTED

gopherbot pushed a commit that referenced this issue Mar 16, 2023
…R_CALL_NOT_IMPLEMENTED

These error codes are returned on windows in case a particular functions
is not supported.

Updates #41198

Change-Id: Ic31755a131d4e7c96961ba54f5bb51026fc7a563
Reviewed-on: https://go-review.googlesource.com/c/go/+/476916
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/476917 mentions this issue: cmd/go/internal/lockedfile/internal/filelock: use errors.ErrUnsupported

gopherbot pushed a commit that referenced this issue Mar 17, 2023
All platform specific errors are now covered by errors.ErrUnsupported.

Updates #41198

Change-Id: Ia9c0cad7c493305835bd5a1f349446cec409f686
Reviewed-on: https://go-review.googlesource.com/c/go/+/476917
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Tobias Klauser <tobias.klauser@gmail.com>
@bcmills
Copy link
Contributor

bcmills commented May 3, 2023

I think all that's remaining here for the release is to decide whether net/http.ErrNotSupported should match errors.ErrUnsupported. I suspect that it should, but probably @neild should be the one to make the final decision on that.

@bcmills
Copy link
Contributor

bcmills commented May 3, 2023

At some point someone should also decide whether to update:

But they're not as closely tied to the release cycle,

@neild
Copy link
Contributor

neild commented May 3, 2023

I agree that net/http.ErrNotSupported should match errors.ErrUnsupported. It could unwrap to errors.ErrNotUnsupported, or perhaps we should just define ErrNotSupported = errors.ErrUnsupported.

@bcmills
Copy link
Contributor

bcmills commented May 3, 2023

We can't define http.ErrNotSupported = errors.ErrUnsupported because the existing value has type *http.ProtocolError, but we can certainly make it unwrap to errors.ErrUnsupported.

@ianlancetaylor
Copy link
Contributor Author

Technically I think that making net/http.ErrNotSupported match errors.ErrUnsupported will require defining either an Is or an Unwrap method on net/http.ProtocolError. So that is new API, which technically requires a proposal. Anybody think we can skip the proposal here?

@ianlancetaylor
Copy link
Contributor Author

I looked at x/net/webdav.ErrNotImplemented. This is an example of the pattern that @rsc described at #41198 (comment): an optional method is permitted to provide a more efficient operation, and if it returns ErrNotImplemented then a standardized approach is used instead. This error should not match ErrUnsupported.

x/net/websocket/ErrNotImplemented is not used, but if it were used it should probably not match ErrUnsupported.

x/net/websocket/ErrNotSupported is used by websocket.Message. The latter is defined to support marshaling string and []byte values only. If you try to marshal something else, you get ErrNotSupported. To me this seems different from errors.ErrUnsupported, which is about cases that might sometimes work but, in a particular case, don't. The websocket.ErrNotSupported seems more like programmer error. It's a case that will never work.

So I don't think we need to change anything in these packages. Happy to hear counter-arguments.

@neild
Copy link
Contributor

neild commented May 10, 2023

http.ErrNotSupported is used in two places:

  1. ResponseController methods return "an error matching ErrNotSupported" when a method is not supported. We could just make these methods return something which wraps http.ErrNotSupported and errors.ErrUnsupported.
  2. Pusher.Push returns ErrNotSupported when server push is not supported. This one would require a change to http.ProtocolError.

So that is new API, which technically requires a proposal. Anybody think we can skip the proposal here?

I would really like to have an expedited review process for trivial API changes, but so long as we're requiring external contributors to go through full proposal review for similarly small changes I don't think we should skip it ourselves.

But maybe we can consider it an addendum to this proposal rather than an entirely new one?

@ianlancetaylor
Copy link
Contributor Author

Yes, I think I'm ready to call it an addendum to this proposal. Sending a CL.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/494122 mentions this issue: net/http: let ErrNotSupported match errors.ErrUnsupported

gopherbot pushed a commit that referenced this issue May 10, 2023
For #41198

Change-Id: Ibb030e94618a1f594cfd98ddea214ad7a88d2e73
Reviewed-on: https://go-review.googlesource.com/c/go/+/494122
Auto-Submit: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@heschi
Copy link
Contributor

heschi commented May 17, 2023

We think the the CL above has closed this issue.

@heschi heschi closed this as completed May 17, 2023
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/498775 mentions this issue: doc/go1.21: mention errors.ErrUnsupported

gopherbot pushed a commit that referenced this issue May 30, 2023
Also mention errors that implement it.

For #41198

Change-Id: I4f01b112f53b19e2494b701bb012cb2cb52f8962
Reviewed-on: https://go-review.googlesource.com/c/go/+/498775
Reviewed-by: Eli Bendersky <eliben@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
@golang golang locked and limited conversation to collaborators May 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests