-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: change standard library to check for io.EOF using errors.Is #39155
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
Comments
I believe that |
The standard library says |
The issue I'm hitting is definitely with
The semantic question in here is what does "is the error" mean after go1.13 . Is an error that is wrapped still the same error? From the method names (and design objectives) added in go1.13 it would strongly suggest that it is.
All the As I described, after this would ship, and a 3rd party library decides to wrap an IO error, they could break another 3rd party library that upgrades to use them. But 3rd party libraries are not under any go1 backward compatibility guarantees. Nobody can be sure they don't decide to not implement It will definitely take time for all people to always use
My issue definitely is that there is no way to add typed information to errors that arise through io processing (at least without forking a significant portion of stdlib). If there are other cases they are less likely to actually solve the issues. |
Personally I don't think we have that leeway. I think that the contract of the standard and widely-used That said, I think you are suggesting that where the standard library calls I don't think that would do any real harm but I'm not personally convinced that it is necessary, given my reading of how I think Happy to hear other opinions. |
I'm curious to understand how adjusting the docs about |
Unless we change all code to use
It's not an issue of whether tooling can migrate existing code. It's a question of whether correct code today will continue to function after the documented change. Most code today only do an |
TL;DR — I have a general rule of thumb that I don't return I tend to think of Because of this, I don't think I've ever seen a place where checking for This means that if I really want to check for |
This is only true if the "file" is not infinite. It is the user/developer that has the expectation that there will ever be an end-of-file, not the |
This PR is not only to correct existing error comparison (e.g. e1 == e1), but also to enable linting check. Excetion: - Skip the error comparision in test file - Skip io.EOF as per golang/go#39155 Signed-off-by: Tam Mach <[email protected]>
…12707) This PR is not only to correct existing error comparison (e.g. e1 == e1), but also to enable linting check. Excetion: - Skip the error comparision in test file - Skip io.EOF as per golang/go#39155 Signed-off-by: Tam Mach <[email protected]>
I was bitten by this, when implementing a I think the stdlib should use It's a valid concern that third-party packages may also be doing equality checks instead of I also think |
I don’t understand the association. io.EOF is a sentinel value, it’s defined in the various io interfaces that it must be returned on end of file. There is no need to use errors.Is because by definition io.EOF must not be wrapped. |
The Is there a downside to using |
The downside is API confusion. Once we split the ecosystem, some clients of It's better if the stdlib keeps using Why do you want to wrap |
It's not so much that I want to wrap |
If the consensus is that certain errors (including The alternative, to encourage |
If you’re implementing an io reader or writer it’s unavoidable. If you’re working at a layer above then that is the place to normalise io.EOF handling, see bufio.Scanner as an example. At that point you or you’re caller doesn’t need to know anything more about the error than it was nil, indicating success, or non nil, a failure. |
Would you support a patch which causes |
Could you tell me more about your debugging process. In the case of io.Reader the logic usually goes
could you explain how a wrapped io.EOF caused you to loose time debugging? maybe the solution is not to outlaw |
I was using
This produced corrupt output from The function now reads:
I'm not sure this is correct, because other errors ( If |
Heh, and in "fixing" it, I just realized I introduced a bug where |
Note that if err != nil && !errors.Is(err, io.EOF) Can be written as if err != nil && err != io.EOF Which I realise is kind of the point you’re making, but it’s also my point — when you operate at this level — interposition your reader on top of another — you have to maintain the io.EOF invariant |
Wouldn't it be easier if we didn't have to maintain that invariant? Saying "always use Maintaining a list of "unwrappable" errors in documentation and/or tooling seems really hard. I'm offering to do the work to make the standard library easier to use, by replacing |
Possibly, but not backward compatible with go 1 |
I'm not sure I follow, especially after reading Ian's comment on May 19 which addressed this exact point. I don't believe anyone is proposing changing the return value of |
Io.Reader implementations must return io.EOF on eof conditions (as you’ve found), so there is no reason to use errors.Is(err, io.EOF) when a simple comparison will work just fine. The go 1 constraft prohibits changing the former, so there’s no value to be realised from the latter. |
If that's the case, can we at least agree that |
I’d encourage you raise that as a separate proposal. |
Done, thanks! |
That's a horribly bad idea. I might use |
It might be possible to write a vet-check (or any of the gajillion third-party static analysis tools) that tries to find code that returns a wrapped |
This proposal has been added to the active column of the proposals project |
Based on the discussion above, this proposal seems like a likely decline. |
No change in consensus, so declined. |
Go1.13 introduced
errors.Is/As/Unwrap
for adding typed information to errors. With this change, errors turn into chains of values instead of a plain string. I think this is a significant improvement, but one of the issues stopping us from getting the full benefit from this is that the error checks inside stdlib still use the old==
equality check instead oferrors.Is
.This means that whenever we wrap errors with extra type information, we need to make sure these errors are not returned by any interface that might be passed to stdlib.
The most common case for this is
io.Reader
that should returnio.EOF
when there is no more data. With a quick search, I found that there are over 200 places in stdlib today whereEOF
is checked with==
condition. For a typical example,io.Copy
uses a check like this to determine if reader error also becomes a copy error. If we would change all these places to useerrors.Is(err, io.EOF)
instead, custom implementations ofio.Reader
could return much more useful errors.For example,
http.RoundTripper
implementation could associate the request/response body with the information about the request, so that when an error happens later, it contains information about what specific request produced the data that ended up failing. Currently, most of the time, we would just get anunexpected EOF
orbroken pipe
in this case.I'm not suggesting any of the current
io.EOF
errors returned from stdlib (eg. os.File) should be wrapped. That would not be backward compatible. But just changing the internal equality checks and documenting it should be harmless.Initially, 3rd party libraries will still likely check
io.EOF
directly, but over time they will get updated as well. Before stdlib makes the change and provides an official recommendation, they don't have any incentive to do that.The text was updated successfully, but these errors were encountered: