在Golang中连续运行io.Copy(os.Stdout,&r)时得到不同的结果

I am playing around Golang. About io.Copy I put 2 consecutive io.Copy in the code, but i expect it output twice result(testtesttest). But the 2nd one is nil. Can anyone help explain why? tks

package main

import (
    "io"
    "os"
    "strings"
    "fmt"

)

type testReader struct {
    w io.Reader
    str string

}


func (tt *testReader) Read (b []byte) (n int, err error) {


    io.Copy(os.Stdout, tt.w)
     n, err = tt.w.Read(b)
     if tt.w !=nil {
        return 0,io.EOF
      }
    return
}

func main() {
    s := strings.NewReader("testtesttest!!!")
    r := testReader{s,"ttthhh"}
    fmt.Println(&r)
    io.Copy(os.Stdout, &r)
//  s.Seek(0,0)   // solution from Poy's answer
    io.Copy(os.Stdout, &r)

}

I'm going to prune down the given example to (as there is a bit of noise):

package main

import (
    "io"
    "os"
    "strings"
)

func main() {
    s := strings.NewReader("testtesttest")
    io.Copy(os.Stdout, s) // Will print "testtesttest"
    io.Copy(os.Stdout, s) // Won't print anything
}

The reason the second copy won't output anything is the io.Reader (s) has already been read. Reading from a io.Reader is not idempotent (you can't call it twice to get the same results). It also doesn't have a way to "reset" it or anything.

As @JRLambert pointed out you have s.Seek() and s.Reset() to allow you to start reading again.

Quick addition to all the correct answers (@poy and @JRLambert) provided so far... Use io.TeeReader or io.MultiWriter for times when you would want to use io.Copy more than once. Below are some examples of using each.


Using io.TeeReader

package main

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

func main() {
    sourceFile, _ := os.Open("source/ebook.pdf")

    var buf bytes.Buffer
    tee := io.TeeReader(sourceFile, &buf)

    process := func(sourceReader io.Reader) {
        targetFile, _ := os.Create("target/ebook.pdf")
        defer targetFile.Close()
        if _, err := io.Copy(targetFile, sourceReader); err != nil {
            fmt.Println(err)
        }
    }

    process(tee)
    fmt.Println(checksum(&buf))
}

func checksum(buf *bytes.Buffer) string {
    h := md5.New()
    b, _ := ioutil.ReadAll(buf)
    if _, err := h.Write(b); err != nil {
        fmt.Println(err)
    }
    return hex.EncodeToString(h.Sum(nil)[:16])
}


Using io.MultiWriter

package main

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

func main() {
    sourceFile, _ := os.Open("source/ebook.pdf")

    process := func(sourceReader io.Reader) {
        targetFile, _ := os.Create("target/ebook.pdf")
        defer targetFile.Close()

        var buf1, buf2 bytes.Buffer
        w := io.MultiWriter(targetFile, &buf1, &buf2)

        if _, err := io.Copy(w, sourceReader); err != nil {
            fmt.Println(err)
        }

        fmt.Println(checksum(&buf1))
        fmt.Println(checksum(&buf2))
    }

    process(sourceFile)

}

func checksum(buf *bytes.Buffer) string {
    h := md5.New()
    b, _ := ioutil.ReadAll(buf)
    if _, err := h.Write(b); err != nil {
        fmt.Println(err)
    }
    return hex.EncodeToString(h.Sum(nil)[:16])
}