实现 io.Reader 时无限循环中 break 与 return 的不同行为

Different behaviour of break vs. return in infinite loop when implementing io.Reader

我正在进行官方巡演。今天,我在做rot13reader exercise时遇到了一些奇怪的事情。

问题是当我使用 break 而不是 return ttl, io.EOF 时,程序进入了无限循环。然而,据我所知,在这个程序中,breakreturn ttl, io.EOF 应该没有区别,因为如果它是 break,下一行将是 return ttl, err Read() 方法的结尾,与 return ttl, io.EOF 相同。

我想知道为什么。与 Go 如何处理 io.Reader 接口及其实现的底层机制有关吗?

这是代码。

package main

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

type rot13Reader struct {
    r io.Reader
}

func (rr *rot13Reader) Read(b []byte) (n int, err error) {
    rb := make([]byte, 8)
    var ttl int
    for {
        n, err := rr.r.Read(rb)
        if err == io.EOF {
            return ttl, io.EOF
            // break <----------------------------here's the problem
        } else if err != nil {
            panic(err)
        } else {
            for i, c := range rb[:n] {
                b[i+ttl] = decodeRot13(c)
            }
            ttl += n
        }
    }
    return ttl, err
}

func decodeRot13(c byte) byte {
    if c >= 97 && c <= 122 { // a-z: 97 122
        c += 13
        if c > 122 {
            c -= 26
        }
    } else if c >= 65 && c <= 90 { // A-Z: 65 90
        c += 13
        if c > 90 {
            c -= 26
        }
    }
    return c
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

观察到的行为是由于变量阴影造成的:

func (rr *rot13Reader) Read(b []byte) (n int, err error) { // <-- this 'err'
    rb := make([]byte, 8)
    var ttl int
    for {
        n, err := rr.r.Read(rb) // <-- and this 'err' are different
        if err == io.EOF {
            return ttl, io.EOF
            // break <----------------------------here's the problem
        } else if err != nil {
            panic(err)
        } else {
            for i, c := range rb[:n] {
                b[i+ttl] = decodeRot13(c)
            }
            ttl += n
        }
    }
    return ttl, err
}

这一行:

        n, err := rr.r.Read(rb) // <-- and this 'err' are different

由于 := 分配,创建了 err 的一个新实例,它隐藏了在更高范围定义的实例。这意味着当您退出 for 循环时,此版本的 err 不可用,并且使用设置为 nil 的更高范围的版本。

这就是为什么 return ttl, errreturn ttl, nil 相同,而与 return ttl, io.EOF 完全不同的原因。