I was learning go and doing some tests with the language when I found this weird behavior in my code. I create two types to demonstrate my findings and implemented the interface io.Write
on both. This little program downloads the content of a web page and prints it to the console.
The BrokenConsoleWriter
keeps track of the bytes written by fmt.Println
and any error it may "throw" and returns it. On the other hand, the ConsoleWriter
simply ignore the return of fmt.Println
and returns the total length of the slice and nil for the error.
When I run the program, the BrokenConsoleWriter
doesn't print the entire html content while that ConsoleWriter
does. Why is this happening?
package main
import (
"fmt"
"io"
"net/http"
"os"
)
type ConsoleWriter struct{}
type BrokenConsoleWriter struct{}
func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
bytesWritten, error := fmt.Println(string(p))
return bytesWritten, error
}
func (c ConsoleWriter) Write(p []byte) (n int, err error) {
fmt.Println(string(p))
return len(p), nil
}
func main() {
const url = "https://www.nytimes.com/"
resp, err := http.Get(url)
if err != nil {
fmt.Println("Some error occurred:", err)
os.Exit(1)
}
//io.Copy(ConsoleWriter{}, resp.Body)
io.Copy(BrokenConsoleWriter{}, resp.Body)
}
CodePudding user response:
The Write()
method is to implement io.Write()
which documents that:
Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily.
Implementations must not retain p.
So your Write()
method must report how many bytes you processed from the p
slice passed to you. Not how many bytes you generate to some other source.
And this is the error with your BrokenConsoleWriter.Write()
implementation: you don't report how many bytes you process from p
, you report how many bytes fmt.Prinln()
actually writes. And since fmt.Prinln()
also prints a newline after printing its arguments, the value it returns will surely be not valid for BrokenConsoleWriter.Write()
.
Note that fmt.Prinln()
with a single string
argument will write out that string and append a newline, which on unix systems is a single character \n
, and \r\n
on Windows. So on unix systems you also get a correct behavior if you subtract 1
from its return value:
func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
bytesWritten, error := fmt.Println(string(p))
return bytesWritten - 1, error
}
Also note that the input is already formatted into lines, so you inserting newlines "randomly" by using fmt.Prinln()
may even result in an invalid document. Do use fmt.Print()
instead of fmt.Println()
:
func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
bytesWritten, error := fmt.Print(string(p))
return bytesWritten, error
}
But the correctness of this solution still depends on the implementation of fmt.Print()
. The correct solution is to report len(p)
because that's what happened: you processed len(p)
bytes of the input slice (all of it).