← Debugging Guides

阅读中文版本

Go Nil Interface Method Call — Panic at Runtime

Fix "invalid memory address or nil pointer dereference" from calling a method on a nil interface by returning typed nils only when callers expect them.

intermediatedebugginggointerfacenil

Symptoms

  • panic: runtime error: invalid memory address or nil pointer dereference
  • The stack points at a method call on an interface value
  • The interface "is not nil" in logs yet the method panics

Possible causes

  • A typed nil (*T)(nil) is stored in an interface, so the interface itself is non-nil but the method dereferences nil
  • A function returns an interface without a valid implementation and the caller calls a method
  • A dependency is injected as nil and used without a guard

Diagnosis steps

Print the interface with type information

fmt.Printf("%T %v\n", iface, iface)

Interpretation

A non-nil type with a nil value shows a typed nil, explaining why the "not nil" check passed but the call panicked.

Reproduce the nil interface call

go run - <<'EOF'
package main
import "fmt"
type I interface{ Do() }
type T struct{}
func (T) Do(){ fmt.Println("do") }
func bad() I { var p *T; return p } // typed nil
func main(){ i := bad(); if i != nil { i.Do() } }
EOF

Interpretation

This minimal program panics despite the nil check, proving the typed-nil trap.

Root cause

An interface value is nil only when both its type and value are nil. Returning a typed nil pointer assigns a concrete type to the interface, making the interface non-nil while the underlying receiver is still nil, so the method call dereferences nil.

Fix

Return a true nil interface (return nil, not a typed nil) at the point a dependency is absent, and use the comma-ok or a small sentinel implementation instead of comparing interfaces to nil.

Risk note

Comparing an interface to a typed nil constant compiles but never matches, so the guard silently fails.

Verify the fix

Rerun the minimal program and confirm it no longer panics and prints the guard branch. Add a test asserting the constructor returns a nil interface on the absent path.

Symptoms

The error “Go Nil Interface Method Call — Panic at Runtime” points at a specific failure mode. This guide documents the symptoms you can observe, the commands that isolate the cause, the root cause, the fix, and the checks that prove the fix works.

Observed symptoms include:

  • panic: runtime error: invalid memory address or nil pointer dereference
  • The stack points at a method call on an interface value
  • The interface “is not nil” in logs yet the method panics

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Print the interface with type information

fmt.Printf("%T %v\n", iface, iface)

A non-nil type with a nil value shows a typed nil, explaining why the “not nil” check passed but the call panicked.

Step 2: Reproduce the nil interface call

go run - <<'EOF'
package main
import "fmt"
type I interface{ Do() }
type T struct{}
func (T) Do(){ fmt.Println("do") }
func bad() I { var p *T; return p } // typed nil
func main(){ i := bad(); if i != nil { i.Do() } }
EOF

This minimal program panics despite the nil check, proving the typed-nil trap.

Likely contributors, in order of frequency:

  • A typed nil (*T)(nil) is stored in an interface, so the interface itself is non-nil but the method dereferences nil
  • A function returns an interface without a valid implementation and the caller calls a method
  • A dependency is injected as nil and used without a guard

Root Cause

An interface value is nil only when both its type and value are nil. Returning a typed nil pointer assigns a concrete type to the interface, making the interface non-nil while the underlying receiver is still nil, so the method call dereferences nil.

The Fix

Return a true nil interface (return nil, not a typed nil) at the point a dependency is absent, and use the comma-ok or a small sentinel implementation instead of comparing interfaces to nil.

Watch out: Comparing an interface to a typed nil constant compiles but never matches, so the guard silently fails.

type Store interface{ Save() error }

func newStore(enabled bool) Store {
	if !enabled {
		return nil // true nil interface, not (*kvStore)(nil)
	}
	return &kvStore{}
}

s := newStore(false)
if s != nil { // correct: s is a genuine nil interface
	_ = s.Save()
}

Verify and Prevent

Rerun the minimal program and confirm it no longer panics and prints the guard branch. Add a test asserting the constructor returns a nil interface on the absent path.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.