gin-gonic/gin · error

Bind struct can not be a pointer. Example: Use: gin.Bind(St

Error message

Bind struct can not be a pointer. Example:
	Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})

What it means

Thrown by gin.Bind in utils.go:32 when the value passed to Bind is a reflect.Pointer. gin.Bind is a middleware factory: it records the struct TYPE so that, on each request, it can reflect.New(typ) a fresh zero-value to deserialize into. Passing &Struct{} would cause every request to share the same instance, so the function refuses a pointer and asks for the value form. The message shows the intended usage: gin.Bind(Struct{}) not gin.Bind(&Struct{}).

Source

Thrown at utils.go:32

	"runtime"
	"strings"
	"unicode"
)

// BindKey indicates a default bind key.
const BindKey = "_gin-gonic/gin/bindkey"

// localhostIP indicates the default localhost IP address.
const localhostIP = "127.0.0.1"

// localhostIPv6 indicates the default localhost IPv6 address.
const localhostIPv6 = "::1"

// Bind is a helper function for given interface object and returns a Gin middleware.
func Bind(val any) HandlerFunc {
	value := reflect.ValueOf(val)
	if value.Kind() == reflect.Ptr {
		panic(`Bind struct can not be a pointer. Example:
	Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})
`)
	}
	typ := value.Type()

	return func(c *Context) {
		obj := reflect.New(typ).Interface()
		if c.Bind(obj) == nil {
			c.Set(BindKey, obj)
		}
	}
}

// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
func WrapF(f http.HandlerFunc) HandlerFunc {
	return func(c *Context) {
		f(c.Writer, c.Request)
	}

View on GitHub (pinned to 34dac209ff)

Solutions

  1. Pass the struct by value: gin.Bind(LoginRequest{}). Bind records the type and allocates a fresh pointer per request internally.
  2. If you need binding but not as middleware, use c.ShouldBind(&obj) inside the handler instead of gin.Bind.
  3. Double-check you are calling gin.Bind (package-level middleware factory) and not c.ShouldBind / c.Bind (per-request methods on Context) — the conventions differ.

Example fix

// before
router.Use(gin.Bind(&LoginRequest{})) // panics: pointer not allowed

// after — pass by value, Bind allocates per request
router.Use(gin.Bind(LoginRequest{}))

// alternative — bind inside the handler with a pointer (correct here)
router.POST("/login", func(c *gin.Context) {
    var req LoginRequest
    if err := c.ShouldBind(&req); err != nil { c.Status(400); return }
    // ...
})
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify the value passed to gin.Bind is not a pointer before calling it.
func safeBind(v any) (gin.HandlerFunc, error) {
    if reflect.ValueOf(v).Kind() == reflect.Ptr {
        return nil, fmt.Errorf("gin.Bind requires a value, got pointer %T", v)
    }
    return gin.Bind(v), nil
}

Type guard

// isBindValue reports whether v is an acceptable argument for gin.Bind.
func isBindValue(v any) bool {
    return reflect.ValueOf(v).Kind() != reflect.Ptr
}

Try / catch

func mustBind(v any) (mw gin.HandlerFunc) {
    defer func() {
        if r := recover(); r != nil {
            panic(fmt.Errorf("gin.Bind(%T): %v\nUse gin.Bind(Struct{}) not gin.Bind(&Struct{})", v, r))
        }
    }()
    return gin.Bind(v)
}

Prevention

When it happens

Trigger: Calling router.Use(gin.Bind(&LoginRequest{})) or gin.Bind(&User{}) at router setup time. Any time the argument to gin.Bind is an address-of expression rather than a struct literal.

Common situations: Migrating from c.ShouldBind(&obj) (where a pointer IS correct at call site) to the Bind middleware factory and passing the pointer out of habit; copy-pasting a struct literal that already had '&' in front; reading examples for the wrong Bind API.

Related errors


AI-assisted analysis of gin-gonic/gin@34dac209ff (2026-08-04). Data as JSON: /data/errors/b5de9eb1ce770542.json. Report an issue: GitHub.