-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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
doc: Effective Go has a bad/unreliable idiom #27818
Comments
Change https://golang.org/cl/140957 mentions this issue: |
The loop follows
|
I think that's correct. I've tried several times to write a thing that's both correct and clear. I had come up with
but then I feel like it makes sense to comment on the rationale for the break, and that takes more space and clutters the example. I think your solution is clean as self-contained code, but it violates the same Principle of Least Astonishment that It gets worse if you even consider trying to handle things like I've been testing it with a test harness of sorts on Playground: https://play.golang.org/p/uVJctuzC9AX The intent being that it should get all 32 bytes for both "easy" and "hard", but correctly report the error for "fail". In any event, it does stay annoyingly difficult. My best attempt at commenting the
... note also that the "nbytes < 1" test is exploiting the fact that you can't get a short read without some kind of error, and there's no partial fills of a single-byte read. Otherwise, it would still be necessary to move |
It stops reading on an error and sets err, as the original Read(32) would do. Why should we try to do more than the Read(32) that immediately precedes this in the text?
Yes, an error indicates a problem, but for reading it doesn't mean it's failed. The caller of Read(32) knows the context and can decide what's a failure. Perhaps 17, EOF is failure, perhaps not. The Read(32)-as-a-loop expansion shouldn't be trying to embed this caller's knowledge IMO as it didn't follow the Read(32) either, and making up possible context and dealing with it just clutters. Given an io.Reader may return 0,nil and it's to documented as a no-op, I think the fault with the Read(1) loop is it doesn't The text leading to the loop says 'read the first 32 bytes of the buffer' and could do with an 'up to'. |
This handles a no-op return from Read.
|
Wait, Reader can return {0,nil}? I thought... huh. Okay, re-reading this, I am now very confused, I was pretty sure that it was mandated that err had to be non-nil if n was less than the length. But it's not!
So I was just plain wrong on that. I am probably too used to POSIX semantics, in which a return of exactly 0 bytes is EOF, because an error that wasn't EOF and prevented any reading would be returned as -1. So, implementations are "discouraged" from yielding {0,nil}, but that doesn't mean they won't. I think your code handles all the relevant cases; if an EOF comes in along with the successful read, we'll end up with {32, EOF}, which is a successful read of 32 bytes, and that seems correct. I sort of wish the initial Read spec had been slightly more dogmatic about what is permitted. Re-reading it, your code is also clearer as to intent; it's not retrying exactly 32 times, it's running until it gets an error or gets 32 bytes, which could take more than 32 loops, depending on the reader. It won't handle a |
Change https://golang.org/cl/143677 mentions this issue: |
The CL's comment mentions endless conversation around the subtleties of Read semantics. That was caused by lack of awareness that the (int, error) return values are orthogonal and (0, nil) is a no-op. I expect that's common amongst Go programmers. The CL promotes that misunderstanding by giving a prominent example of Read reading zero bytes being merged with an error. It could have used the opportunity to widen the knowledge of the no-op return. |
That might be a good thing to discuss in Effective Go, but I don't think it's a good thing to try to explain in the slice example. |
What version of Go are you using (
go version
)?1.11
Does this issue reproduce with the latest release?
N/A, but yes
What operating system and processor architecture are you using (
go env
)?N/A, but linux/amd64
What did you do?
Read "Effective Go" again. Specifically, the slice example:
What did you expect to see?
A good example of Effective Go. :)
What did you see instead?
A program which will behave incorrectly in the fairly common case where an io.Reader can return a non-fatal or "benign" error along with a non-zero byte count.
iotest.DataErrReader
does this explicitly, but I believe at least one of the commonly-used Reader interfaces does it already; the Read which reaches EOF can return io.EOF along with a full read.suggested fix: just drop the
|| e != nil
; short reads are impossible when the requested number of bytes is 1. If nbytes is 0, an error must have occurred and we don't need to check whether e is nil, and if nbytes is 1, e can be non-nil without implying that there's a problem. (It would usually be io.EOF in that case, so a break is still reasonable, but setting err may be misleading. Usually if the read is successful, an io.EOF gets ignored rather than bubbling up as an error state.)The text was updated successfully, but these errors were encountered: