cayleygraph/cayley · error

nil destination object

Error message

nil destination object

What it means

Config.LoadToDepth loads graph nodes into a destination object via reflection. A nil dst interface gives reflection nothing to write into, so the loader fails fast with this error before touching the quad store.

Source

Thrown at schema/loader.go:100

// All fields in structs are interpreted as required (except slices), thus struct will not be
// loaded if one of fields is missing. An "optional" tag can be specified to relax this requirement.
// Also, "required" can be specified for slices to alter default value.
//
//	type Person struct{
//		ID quad.IRI `json:"@id"`
//		Name string `json:"name"` // required field
//		ThirdName string `quad:"thirdName,optional"` // can be empty
//		FollowedBy []quad.IRI `quad:"follows"`
// 	}
func (c *Config) LoadTo(ctx context.Context, qs graph.QuadStore, dst interface{}, ids ...quad.Value) error {
	return c.LoadToDepth(ctx, qs, dst, -1, ids...)
}

// LoadToDepth is the same as LoadTo, but stops at a specified depth.
// Negative value means unlimited depth, and zero means top level only.
func (c *Config) LoadToDepth(ctx context.Context, qs graph.QuadStore, dst interface{}, depth int, ids ...quad.Value) error {
	if dst == nil {
		return fmt.Errorf("nil destination object")
	}
	var it iterator.Shape
	if len(ids) != 0 {
		fixed := iterator.NewFixed()
		for _, id := range ids {
			idv, err := qs.ValueOf(id)
			if err != nil {
				return err
			}
			fixed.Add(idv)
		}
		it = fixed
	}
	var rv reflect.Value
	if v, ok := dst.(reflect.Value); ok {
		rv = v
	} else {
		rv = reflect.ValueOf(dst)

View on GitHub (pinned to 81dcd7d73e)

Solutions

  1. Pass a pointer to an allocated struct: dst := &MyType{}; then c.LoadTo(ctx, qs, dst, id...).
  2. Guard the call site with if dst != nil before invoking LoadTo/LoadToDepth.
  3. If dst is a pointer variable, initialize it with new(MyType) or a composite literal.

Example fix

// before
var dst *Person
c.LoadTo(ctx, qs, dst, quad.IRI("/person/alice"))
// after
dst := &Person{}
c.LoadTo(ctx, qs, dst, quad.IRI("/person/alice"))
Defensive patterns

Strategy: type-guard

Validate before calling

if dst == nil {
    return errors.New("destination must be a non-nil struct pointer")
}
if rv := reflect.ValueOf(dst); rv.Kind() != reflect.Ptr || rv.IsNil() {
    return errors.New("destination must be a pointer to struct")
}

Type guard

func isNonNilStructPtr(dst interface{}) bool {
    rv := reflect.ValueOf(dst)
    return rv.Kind() == reflect.Ptr && !rv.IsNil() && rv.Elem().Kind() == reflect.Struct
}

Try / catch

if err := c.LoadTo(ctx, qs, dst, ids...); err != nil {
    if err.Error() == "nil destination object" {
        return fmt.Errorf("caller bug: uninitialized destination: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling LoadTo/LoadToDepth(ctx, qs, dst, ...) with a nil interface{} or an untyped nil variable as dst.

Common situations: Declaring var dst *MyType (nil pointer) instead of dst := &MyType{}; passing a nil result from another call straight into LoadTo.

Related errors


AI-assisted analysis of cayleygraph/cayley@81dcd7d73e (2026-09-06). Data as JSON: /api/errors/7872c9431bec837e. Report an issue: GitHub.