如何正确检查 io.Reader 是否为 nil?

huangapple go评论78阅读模式
英文:

How to correctly check io.Reader for nil?

问题

如果你创建一个类型为bytes.Buffer的变量(未初始化),并将其赋值给类型为io.Reader的字段,然后在检查io.Reader是否为nil之后,会出现错误:invalid memory address or nil pointer dereference。如何正确检查以避免此类错误?

package main

import (
	"bytes"
	"io"
	"io/ioutil"
)

type Request struct {
	Body io.Reader
}

func main() {
	var data *bytes.Buffer

	request := &Request{
		Body: data,
	}

	if request.Body != nil {
		ioutil.ReadAll(request.Body) // panic: runtime error: invalid memory address or nil pointer dereference
	}
}

Playground

英文:

If you create a variable of type bytes.Buffer (without initialization) and assign it to a field of type io.Reader, then after checking io.Reader for nil there will be an error: invalid memory address or nil pointer dereference. How to check this correctly to avoid such errors?

Playground

package main

import (
	"bytes"
	"io"
	"io/ioutil"
)

type Request struct {
	Body io.Reader
}

func main() {
	var data *bytes.Buffer

	request := &Request{
		Body: data,
	}

	if request.Body != nil {
		ioutil.ReadAll(request.Body) // panic: runtime error: invalid memory address or nil pointer dereference
	}
}

答案1

得分: 3

要检查一个io.Reader(或任何其他接口)值是否为nil,你只需将其与nil进行比较。

一个非nilio.Reader是否是一个有意义的实现,这是另一个问题。

例如,这个实现是否有意义?

type panicReader struct{}

func (panicReader) Read(p []byte) (int, error) {
    panic("foo")
}

panicReader显然实现了io.Reader,但无论何时调用它的Read()方法,它都会引发panic。

还有bytes.Buffer。它的指针实现了io.Reader。但是,在nil*bytes.Buffer指针值上调用Buffer.Read()将引发panic。但不是因为你不能在nil指针接收器上调用方法,而是因为bytes.Buffer.Read()的实现尝试解引用指针接收器,这个解引用操作导致了panic:

// bytes.Buffer.Read实现摘录
func (b *Buffer) Read(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    if b.empty() {
    // ...
}

在这里不能得出一个普遍的结论(至少还不能)。看看这个io.Reader的实现:

type myBuffer struct{}

var count int

func (*myBuffer) Read(p []byte) (int, error) {
    if len(p) > 0 {
        count++
        if count >= 10 {
            return 0, io.EOF
        }
        p[0] = 'a'
        return 1, nil
    }
    return 0, nil
}

*myBuffer实现了io.Reader,它的Read()方法不使用指针接收器的值。这意味着什么?你可以在nil*myBuffer值上调用Read()

var data *myBuffer

request := &Request{
    Body: data,
}

if request.Body != nil {
    data, err := ioutil.ReadAll(request.Body)
    fmt.Println(string(data), err)
}

这将输出(在Go Playground上尝试):

aaaaaaaaa <nil>

所以结论是:通常具有指针接收器方法的类型需要一个非nil指针,因为它们使用了被指向的对象(在bytes.Buffer的情况下,它们使用了指向结构体的字段)。为了使用这样的类型(实现接口的有意义的实现),你通常需要一个非nil指针值使方法能够“工作”。然而,正如上面的myBuffer实现所示,这并不总是一个要求。你的工作是始终阅读所使用类型和方法的文档,以避免此类误用(例如尝试使用nil*bytes.Buffer)。

参考相关问题:

https://stackoverflow.com/questions/29138591/hiding-nil-values-understanding-why-go-fails-here/29138676#29138676

https://stackoverflow.com/questions/29988632/go-reflection-with-interface-embedded-in-struct-how-to-detect-real-functions/61448767#61448767

英文:

To check if an io.Reader (or any other interface) value is nil, you simply compare it to nil.

Whether a non-nil io.Reader is a meaningful implementation, that's another question.

E.g. is this implementation meaningful?

type panicReader struct{}

func (panicReader) Read(p []byte) (int, error) {
	panic(&quot;foo&quot;)
}

panicReader certainly implements io.Reader, but whenever you call its Read() method, it will always panic.

There is bytes.Buffer. A pointer to it implements io.Reader. But calling Buffer.Read() on a nil *bytes.Buffer pointer value will panic. But not because you can't call methods on nil pointer receivers, but because the implementation of bytes.Buffer.Read() tries to dereference the pointer receiver, and this dereference operation is what causes the panic:

// Excerpt from bytes.Buffer.Read implementation 
func (b *Buffer) Read(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	if b.empty() {
    // ...
}

You can't make a general conclusion here (just yet). See this io.Reader implementation:

type myBuffer struct{}

var count int

func (*myBuffer) Read(p []byte) (int, error) {
	if len(p) &gt; 0 {
		count++
		if count &gt;= 10 {
			return 0, io.EOF
		}
		p[0] = &#39;a&#39;
		return 1, nil
	}
	return 0, nil
}

*myBuffer implements io.Reader, and its Read() method does not use the pointer receiver value. What does this mean? You can call Read() on a nil *myBuffer value:

var data *myBuffer

request := &amp;Request{
	Body: data,
}

if request.Body != nil {
	data, err := ioutil.ReadAll(request.Body)
	fmt.Println(string(data), err)
}

This will output (try it on the Go Playground):

aaaaaaaaa &lt;nil&gt;

So the conclusion is this: usually types that have methods with pointer receiver require a non-nil pointer because they use the pointed object (in case of bytes.Buffer they use the fields of the pointed struct). To use such types (to have a meaningful implementation of implemented interfaces), you often need a non-nil pointer value for the methods to "work". This–however–is not always a requirement as the above myBuffer implementation shows. It's your job to always read the documentation of the used types and methods to avoid such misuses (e.g. trying to use a nil *bytes.Buffer).

See related question:

https://stackoverflow.com/questions/29138591/hiding-nil-values-understanding-why-go-fails-here/29138676#29138676

https://stackoverflow.com/questions/29988632/go-reflection-with-interface-embedded-in-struct-how-to-detect-real-functions/61448767#61448767

huangapple
  • 本文由 发表于 2022年4月28日 01:06:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/72032762.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定