Search code examples
goencodinggob

gob panics decoding an interface


I have a struct with unexported fields which should be gob encoded and decoded.

Say:

type A struct {
    s int
}
func (a *A) Inc() {
    a.s++
}

Obviously in that case I need to implement gob.GobEncoder and gob.GobDecoder interfaces. And if I use the struct directly everything works fine:

https://play.golang.org/p/dm3HwaI8eU

But I also need to have an interface that implements same logic and is serializable:

type Incer interface {
    gob.GobEncoder
    gob.GobDecoder
    Inc()
}

Full code: https://play.golang.org/p/Zig2mPrnrq

And suddenly it panics:

panic: interface conversion: interface is nil, not gob.GobDecoder [recovered]
    panic: interface conversion: interface is nil, not gob.GobDecoder

But if I comment gob interfaces out everything become fine again.

Am I missing something important? Cos the described behavior seems quite strange to me


Solution

  • The problem lies in the fact that the Incer interface implements a special interface, special to the encoding/gob package: gob.GobDecoder.

    If your Incer interface only contains the Inc() method, it will work because the gob decoder sees you're decoding into an interface type, and it will use the transmitted type to decode the value and check at runtime if the decoded value (whose type is included and transmitted in the stream) implements the destination interface type, and in this case it will:

    type Incer interface {
        Inc()
    }
    

    So decoding succeeds.

    If the Incer interface also embeds the gob.GobEncoder and gob.GobDecoder interfaces, they are –by definition– responsible to do the coding / decoding. If a type implements these interfaces, the decoder will not attempt to decode the value using the transmitted type, but will instead call GobDecode() method of the destination value, creating a zero value if needed.

    Since you pass a nil value to Decoder.Decode(), the decoder needs to create a zero-value, but it doesn't know what type to instantiate, because the value you pass is a pointer to interface. You cannot create a value of interface type, only a value of a concrete type that may satisfy certain interfaces.

    You can't include gob.GobEncoder and gob.GobDecoder in your Incer interface. I know you want to make sure the implementations do implement them, but then –as you can see– you won't be able to decode them into a "general" Incer interface value. Also I don't even see the need to include them in Incer: gob.GobEncoder and gob.GobDecoder are not the only ways to make them transmittable, there are also encoding.BinaryMarshaler and encoding.BinaryUnmarshaler that are checked by the encoding/gob package.