Go Interface Pitfalls: When nil != nil

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 } Program output: isNil: true isNil: false b == nil: true 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 } } 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 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! Follow us on X: @LeapcellHQ Read on our blog

May 10, 2025 - 20:38
 0
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
}

Program output:

isNil:  true
isNil:  false
b == nil:  true

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
  }
}

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