DEV Community

Cover image for Go Interface Pitfalls: When nil != nil
Leapcell
Leapcell

Posted on

1 1 1 1 1

Go Interface Pitfalls: When nil != nil

Cover

During the process of coding in Go, we may encounter a situation where, when using an interface type parameter (such as any or interface{}) to receive other parameters, the given parameter value is clearly nil, yet the inequality check x == nil does not hold. Why is this the case? This article will reveal the mystery.

Case Study

package main

import "fmt"

func main() {
  var a any = nil
  var b *int = nil
  fmt.Println("isNil: ", isNil(a))
  fmt.Println("isNil: ", isNil(b))
  fmt.Println("b == nil: ", b == nil)
}

func isNil(x any) bool {
  return x == nil
}
Enter fullscreen mode Exit fullscreen mode

Program output:

isNil:  true
isNil:  false
b == nil:  true
Enter fullscreen mode Exit fullscreen mode

The Go code example above demonstrates some subtle differences when using the any type (i.e., interface{}) to determine whether a variable is nil. Specifically, it shows why, in some cases, a variable of type any is not considered nil even if its value is nil.

In this code sample, an isNil function is defined with a parameter x any. Then, in the main function, two variables a and b of different types are initialized, both assigned the value nil. Next, they are passed as arguments to the isNil function. Additionally, the result of b == nil is printed directly for comparison.

From the output, we can see that inside the isNil function, a == nil evaluates to true, while b == nil evaluates to false. However, outside the function, b == nil evaluates to true. This is because within the isNil function, the parameter is of type any, which causes x == nil to evaluate as false. To understand the specific reason, we need to look at the internal structure of any. Details below.

The Internal Structure of any (interface{})

In Go, any is an alias for interface{}. Internally, an interface{} value consists of two parts: the type part and the value part.

  • Type part: The concrete type held by the interface. If the interface holds a value of type *int, then the type part is *int.
  • Value part: The actual value held by the interface. If the value is the integer 3, then this part is 3; if it’s nil, then it’s nil.

When a value is assigned to an interface type (like any), the interface stores both the type and the actual value. Only when both the type part and the value part are nil is the interface considered to be nil.

Going back to the earlier code example, when the value of variable b is assigned to an interface variable x, the internal structure of x becomes type = *int and value = nil. Therefore, x == nil evaluates to false.

Checking for nil Using Reflection

Since == or != cannot always reliably determine whether an interface type is nil, how can we solve this problem? The answer is: by using reflection.

With reflection, we can directly check whether the value of a variable is nil.

func isNil(x any) bool {
  if x == nil {
    return true
  }
  value := reflect.ValueOf(x)
  switch value.Kind() {
  case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
    return value.IsNil()
  default:
    return false
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

This article delves into why, when using Go, a variable of interface type may not be considered nil even when its value is nil. Through specific code examples and an analysis of the internal structure of any (i.e., interface{}), we uncover the essence of this phenomenon.

Key takeaways:

  • Interface type internal structure: An any (i.e., interface{}) is composed of a type part and a value part at the underlying level. An interface is considered nil only when both parts are nil.
  • Solution: Use reflection to accurately determine whether an interface-type variable is nil.

We are Leapcell, your top choice for hosting Go projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)

Postmark Image

20% off for developers who'd rather build features than debug email

Stop wrestling with email delivery and get back to the code you love. Postmark handles the complexities of email infrastructure so you can ship your product faster.

Start free

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay