Why must gob.Register be called for a struct field whose type is an interface?
answer
- the wire must name a concrete type
- the decoder cannot invent a type
- both binaries need the same call
- the default name carries the import path
- RegisterName pins it against a refactor
basics
~20 sThe wire has to name the concrete type behind the interface, and the decoder has to turn that name back into a Go type it can allocate. gob.Register records the name-to-type mapping, and both programs must call it.
solid answer
~50 sFor an ordinary field, gob knows the type statically. For an interface-typed field it does not: what is actually there is a dynamic type chosen at runtime, so the encoder has to write a *name* for that concrete type and the decoder has to look the name up to allocate a value of it. `gob.Register(Blob{})` establishes that mapping, and it must exist in both binaries: without it, encoding returns a not-registered error and decoding an unknown name fails too. The default name is derived from the type: its import path plus its type name, so registering the value form and the pointer form produce different names, and moving or renaming the type changes the name for every stream past and future. `gob.RegisterName` pins a stable name explicitly, which is worth doing for anything persisted. Put the calls in an `init` in a package both programs import so they cannot drift.
code
go · 19 linestype Payload interface {
Kind() string
}
type Blob struct {
Data []byte
}
func (Blob) Kind() string { return "blob" }
type Entry struct {
Key string
Body Payload // interface-typed: the concrete type must be registered
}
func init() {
// Pin the name explicitly for data that outlives the binary:
gob.RegisterName("cache.Blob", Blob{})
}go deeper
Know that an interface-typed field needs gob.Register for each concrete type that can appear in it, and that the identical registration must exist in the decoding program too.
Explain what the registration buys: the encoder writes a name for the dynamic type and the decoder looks that name up to allocate the right value. Reflection cannot invent a type that is not linked in.
Talk about where the calls live and what they pin — an init in a shared package so the two binaries cannot drift, and RegisterName for anything persisted so a package move does not orphan yesterday's data.
Recognise the registered name as a published identifier, as binding as any field name on the wire. Once data carrying it is on disk, renaming or relocating the type is a compatibility decision with an owner, not a cosmetic refactor.
## The problem an interface field creates Consider a cache entry that carries a payload whose shape varies: the struct has an exported field declared with an interface type, and at runtime it holds some concrete type that implements that interface. Encoding the struct is fine right up to that field, at which point gob has a question it cannot answer from the static type: *what is actually in there?* The value's dynamic type is only known at runtime, and the decoder on the other end has an even harder version of the same problem — even if the bytes said "this is a Blob", the decoder has to turn the string "Blob" into a Go type it can allocate and assign into the interface field. Go has no facility to conjure a type from a name at runtime. Reflection can inspect types that are linked into the binary; it cannot invent one. So gob requires the program to declare the mapping ahead of time. ## What Register does `gob.Register(value any)` records the concrete type of `value` under a name derived from that type. From then on, when a value of that type is encoded through an interface-typed field, the name goes on the wire alongside the data; when a decoder sees that name, it looks up the registered type and allocates one. Three consequences follow directly: **Both sides must register.** The encoder needs the mapping to write a name; the decoder needs it to resolve one. Registering only in the writing program produces a stream nobody can read. The conventional placement is an `init` function in the package that defines the types, imported by both binaries, so the two cannot drift apart. **You register the concrete types, not the interface.** The interface type is just the static type of the field; it never needs registering. What needs a name is each implementation that might actually appear. **The value form and the pointer form are different types.** `gob.Register(Blob{})` and `gob.Register(&Blob{})` register different names. If one program registers the value and the other the pointer, the name on the wire resolves to nothing on the far side. Registering the same type twice under different names, or two different types under the same name, is a programming error and panics at startup rather than failing later on the wire. ## The registered name is an identifier you have published The default name is built from the type: its import path, then a dot, then the type name (with a leading star for a pointer type). That is convenient and completely automatic — and it means the name silently encodes two things you are otherwise free to change: which package the type lives in, and what the type is called. Move the type to a different package, or rename it, and the name changes. New streams are fine, because both programs move together. Old streams are not: a cache file, a queued message, anything already written under the old name now names a type nothing maps to, and decode fails. `gob.RegisterName(name string, value any)` exists for exactly this. Pinning an explicit, stable name — one that has nothing to do with the current package layout — decouples the wire identifier from the Go identifier, so refactors stay refactors. For anything you persist, this is the safer default; for a short-lived connection between two binaries deployed together, the automatic name is usually fine. ## Failure modes and what they look like - **Encoding an unregistered concrete type through an interface field**: an error naming the type as not registered for the interface. Loud, immediate, easy. - **Decoding a name the local program never registered**: an error at decode. Also loud — but it happens on the *reading* side, possibly in a different service, possibly hours later when a stored file is read back. - **A nil interface field**: no problem. A nil interface is the zero value for the field, so it is simply not transmitted. - **An unexported interface field**: not transmitted at all, registered or not, because the exported-fields rule applies first. ## Where this bites hardest The painful case is not the RPC where both ends restart together; it is data at rest. A shared on-disk cache written by one service and read by another turns every registered name into a compatibility surface with its own lifetime. Registering at `init` in a shared package makes the two programs agree *today*; `RegisterName` is what makes them agree with the bytes written last week.
- One program registers Blob{} and the other registers &Blob{}. Does it work?No. The name is derived from the registered value's type, so the value form and the pointer form register under different names — the pointer's name carries a leading star. The name the encoder writes then resolves to nothing on the decoding side. Register the same form in both programs; a shared init in a common package is the way to guarantee it.
- What happens to the registered name if the type moves to another package?It changes, because the default name is the type's import path plus its name. New data is fine — both programs moved together — but everything already written under the old name now names a type nothing maps to, and those decodes fail. `gob.RegisterName` with a stable string decouples the wire name from the package layout and makes the refactor safe.
- Does the interface type itself need registering?No. Only the concrete types that may appear in the field need names, because those are what actually go on the wire. The interface is the field's static type and gob reads it from the struct definition. The field does still have to be exported, like any other field, or it is skipped entirely.
- What does an interface field holding nil do?Nothing special: a nil interface is the zero value for that field, and gob omits zero-valued struct fields, so the field is simply not transmitted and the destination's field is left alone. No registration is consulted and no error is raised.
An interface-typed field is an unlabelled box. Register is what prints the label on the way out and what tells the receiving warehouse which shelf that label means.
saying these in an interview costs you the question
- Registers the interface type instead of the implementations
- Registers only in the encoding program
- Assumes reflection alone can rebuild an unknown type
- Registers a pointer on one side and a value on the other
- Renames or moves a registered type without pinning its name