go-redis/redis · critical

redis: NewRing nil options

Error message

redis: NewRing nil options

What it means

NewRing panics with 'redis: NewRing nil options' when the supplied *RingOptions is nil. Like NewClient, the constructor (ring.go:626-629) documents and enforces this with a panic because a Ring needs at least the Addrs map to build shards; a nil options struct is a programming defect. The panic happens during ring initialization, before any heartbeat or shard is started.

Source

Thrown at ring.go:628

//
// Ring should be used when you need multiple Redis servers for caching
// and can tolerate losing data when one of the servers dies.
// Otherwise you should use Redis Cluster.
type Ring struct {
	cmdable
	hooksMixin

	opt               *RingOptions
	sharding          *ringSharding
	cmdsInfoCache     *cmdsInfoCache
	heartbeatCancelFn context.CancelFunc
}

// NewRing returns a Redis Ring client to the Redis Server specified by RingOptions.
// Passing nil RingOptions will cause a panic.
func NewRing(opt *RingOptions) *Ring {
	if opt == nil {
		panic("redis: NewRing nil options")
	}
	// Shallow-copy the options: the ring-wide HIMPORT registry is carried
	// through them to shard construction, and reusing one caller-owned
	// RingOptions across several rings must not make the rings share (or
	// clobber each other's) registry.
	optCopy := *opt
	opt = &optCopy
	opt.init()
	// The registry must exist before the first shard is created; shards
	// adopt it in newRingShard.
	opt.himport = newHImportRegistry()

	hbCtx, hbCancel := context.WithCancel(context.Background())

	ring := Ring{
		opt:               opt,
		sharding:          newRingSharding(opt),
		heartbeatCancelFn: hbCancel,

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Always pass a non-nil *RingOptions, at minimum &redis.RingOptions{Addrs: map[string]string{...}}.
  2. Make your options loader return a valid *RingOptions with sane defaults rather than nil; fail loudly in config validation if Addrs is empty.
  3. Guard at the call site: if opt == nil { opt = &redis.RingOptions{Addrs: defaultAddrs} } or surface an error from your factory.
  4. Add a constructor wrapper that validates Addrs is non-empty and returns an error instead of letting go-redis panic.

Example fix

// before
ring := redis.NewRing(loadRingCfg()) // returns nil

// after
opt := loadRingCfg()
if opt == nil {
    opt = &redis.RingOptions{Addrs: map[string]string{"shard1": ":6379"}}
}
ring := redis.NewRing(opt)
Defensive patterns

Strategy: validation

Validate before calling

func newRing(opt *redis.RingOptions) (*redis.Ring, error) {
    if opt == nil {
        return nil, errors.New("ring options must not be nil")
    }
    if len(opt.Addrs) == 0 {
        return nil, errors.New("ring options require at least one addr")
    }
    return redis.NewRing(opt), nil
}

Type guard

func validRingOptions(opt *redis.RingOptions) bool {
    return opt != nil && len(opt.Addrs) > 0
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        log.Fatalf("redis ring init failed: %v", r)
    }
}()
ring := redis.NewRing(opt)

Prevention

When it happens

Trigger: Calling redis.NewRing(nil); passing a *RingOptions variable that was declared but never assigned; a config builder that returns nil RingOptions when no addrs were configured; refactoring that extracts ring construction into a helper returning nil on the empty case.

Common situations: Multi-shard config sourced from YAML/env where the ring section is missing so the loader yields nil; conditional client selection (Ring vs Client vs Cluster) where the Ring branch runs with an unpopulated options pointer; tests that omit the options argument.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/d64974d819b18c51.json. Report an issue: GitHub.