Golang的标准库中有许多有趣的工具包裹koude0实例,例如koude1或koude2。但是,当包装koude3实例时,Close
方法被隐藏了。
这是一个快速代码段,可以通过Inline struct
组合包裹的koude0和原始的koude6来重建koude6接口。
代码
var rc io.ReadCloser = struct {
io.Reader
io.Closer
}{
Reader: r,
Closer: c,
}
这是什么?
Golang中的koude0接口在流数据时是非常强大的抽象。它可用于读取文件,http响应,甚至是使用通用代码的原始字节数组:
func sumAllBytes(r io.Reader) (uint64, error) {
var buff [512]byte
var sum uint64
for {
n, err := r.Read(buff[:])
for i := 0; i < n; i++ {
sum += uint64(buff[i])
}
if errors.Is(err, io.EOF) {
return sum, nil
}
if err != nil {
return sum, err
}
}
}
该方法可用于处理文件,http响应,甚至内存缓冲区:
func main() {
// Process a buffer
buff := bytes.NewReader([]byte{0x01, 0x02, 0x03, 0x04})
sum, err := sumAllBytes(buff)
if err != nil {
log.Fatal(err)
}
log.Println("Sum from buffer:", sum)
// Process a http response
resp, err := http.Get("https://www.google.com")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
sum, err = sumAllBytes(resp.Body)
if err != nil {
log.Fatal(err)
}
log.Println("Sum from http:", sum)
// Process a file
fl, err := os.Open("/some/file/path")
if err != nil {
log.Fatal(err)
}
defer fl.Close()
sum, err = sumAllBytes(fl)
if err != nil {
log.Fatal(err)
}
log.Println("Sum from file:", sum)
}
打扫干净
在许多情况下(例如上面的HTTP和文件实例),必须明确关闭读者以避免资源泄漏。因此,许多资源都使用koude3接口,并且可以通过defer rc.Close()
语句轻松地清理。但是在某些情况下,关闭方法在同一函数上未调用,而是在呼叫者站点的某个地方调用。在该构造中,呼叫者负责清理:
func getDataStream(name string) (io.ReadCloser, error) {
switch name {
case "file":
return os.Open("/some/file")
case "http":
resp, err := http.Get("https://www.google.com/")
if err != nil {
return nil, err
}
return resp.Body, nil
case "buffer":
return io.NopCloser(
bytes.NewReader([]byte{0x01, 0x02, 0x03, 0x04}),
), nil
default:
return nil, errors.New("Invalid data stream name")
}
}
func printSum(streamName string) {
stream, err := getDataStream(streamName)
if err != nil {
log.Fatal(err)
}
defer stream.Close()
sum, err := sumAllBytes(stream)
if err != nil {
log.Fatal(err)
}
log.Printf("Sum of bytes in stream '%s' is '%d\n", streamName, sum)
}
到目前为止一切都很好,没什么可担心的。但是,让我们通过一些流包装扩展此示例:
func getDataStream(name string) (io.ReadCloser, error) {
switch name {
case name == "file":
return os.Open("/some/file")
case name == "http":
resp, err := http.Get("https://www.google.com/")
if err != nil {
return nil, err
}
return resp.Body, nil
case name == "buffer":
return io.NopCloser(
bytes.NewReader([]byte{0x01, 0x02, 0x03, 0x04}),
), nil
// v--- Create a truncated stream by applying limit over the base one ---v
case strings.HasPrefix(name, "limit:"):
r, err := getDataStream(name[6:])
if err != nil {
return nil, err
}
return io.LimitReader(r, 3)
default:
return nil, errors.New("Invalid data stream name")
}
}
不幸的是,此代码没有编译,最终出现此错误:
cannot use io.LimitReader(r, 100) (value of type io.Reader) as type io.ReadCloser in return statement:
io.Reader does not implement io.ReadCloser (missing Close method)
用koude1将koude3包裹起来确实隐藏了原始实例的koude6功能。事实证明,Golang Standard Lib有很多地方发生这种包装。
内联结构到营救
有一个简单的技巧可以将Close
方法从原始读取器带回包装的方法:
func getDataStream(name string) (io.ReadCloser, error) {
switch name {
case name == "file":
return os.Open("/some/file")
case name == "http":
resp, err := http.Get("https://www.google.com/")
if err != nil {
return nil, err
}
return resp.Body, nil
case name == "buffer":
return io.NopCloser(
bytes.NewReader([]byte{0x01, 0x02, 0x03, 0x04}),
), nil
// v--- Create a truncated stream by applying limit over the base one ---v
case strings.HasPrefix(name, "limit:"):
r, err := getDataStream(name[6:])
if err != nil {
return nil, err
}
limitReader := io.LimitReader(r, 3)
return struct {
io.Reader
io.Closer
}{
Reader: limitReader, // Read method will come from the wrapped reader
Closer: r, // Close method will come from the original reader
}, nil
default:
return nil, errors.New("Invalid data stream name")
}
}
它是如何工作的?
inline struct
包含两个embedded fields,一个用于读者,另一个用于近距离。
由于这些字段是匿名的,因此struct本身 从这些字段中方法,就好像在结构上声明了这些字段一样。通过这样做,每当编译器试图将结构施放到某个接口时,它都可以proplation 这些方法满足接口的要求。
在上面的代码中,我们返回一个需要Read
和Close
方法的koude3接口的实例 - 分别是从嵌入式字段借用的。
有趣的是,如果我们将整个koude3用作第二个嵌入式字段,而不是koude0,则编译器(在书面上为1.19)会引起由于晋升的现场成员之间的歧义引起的错误(Read
方法没有促进歧义):
cannot use struct{io.Reader; io.ReadCloser}{…} (value of type struct{io.Reader; io.ReadCloser}) as type io.ReadCloser in return statement:
struct{io.Reader; io.ReadCloser} does not implement io.ReadCloser (missing Read method)