Skip to content

Instantly share code, notes, and snippets.

@jpalawaga
Last active April 7, 2022 04:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpalawaga/ebc74876f311b9ea1b837ced89e9e09f to your computer and use it in GitHub Desktop.
Save jpalawaga/ebc74876f311b9ea1b837ced89e9e09f to your computer and use it in GitHub Desktop.
Some code that will nil an interface's value while leaving its type intact.
package main
import (
"fmt"
"unsafe"
"github.com/google/uuid"
)
// NullInterfaceValue takes an interface ref, constructs an new struct from
// the pointer, allowing us to modify the parts of the pointer, and sets the
// interface's value to nil.
func NullInterfaceValue(x *interface{}) {
e := (*emptyInterface)(unsafe.Pointer(x))
e.word = nil
}
func main() {
var uid interface{} = uuid.New()
NullInterfaceValue(&uid)
switch val := uid.(type) {
case nil:
fmt.Println("value is nil")
case fmt.Stringer:
fmt.Println(val.String())
}
}
// Interfaces below are from the reflect package -- (c) Go Authors 2009/BSD.
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
type tflag uint8
type nameOff int32 // offset to a name
type typeOff int32 // offset to an *rtype
@mmorton
Copy link

mmorton commented Apr 7, 2022

I didn't see a way to comment on your blog post, so I'm posting this here, but you don't need to use the above to run into this situation. If you run the program below, you'll see some of the ways you can encounter this:

package main

import (
	"fmt"
	"reflect"
)

type SomeStruct struct {
	name string
}

func (o *SomeStruct) String() string {
	return o.name
}

var _ fmt.Stringer = (*SomeStruct)(nil)

func main() {
	var c fmt.Stringer = (*SomeStruct)(nil)
	check(c)

	var d *SomeStruct
	var e fmt.Stringer = d
	check(e)

	var f *SomeStruct = nil
	check(f)
}

func describe(i interface{}) {
	fmt.Printf("(v:%v, t:%T)\n", i, i)
}

func check(s fmt.Stringer) {
	fmt.Printf("---\n")
	describe(s)
	if s == nil {
		fmt.Printf("is == nil.\n")
		return
	}
	if reflect.ValueOf(s).IsNil() {
		fmt.Printf("is != nil, reflect value is nil.\n")
		return
	}
	switch s.(type) {
	case nil:
		fmt.Printf("type is nil.\n")
	case fmt.Stringer:
		fmt.Printf("string is %s\n", s.String())
	}
}

If you comment out the IsNil() reflection check, it will panic as expected.

Also, this happens often enough with error (an interface) that it is mentioned in the FAQ: https://go.dev/doc/faq#nil_error.

Nil receivers are by design as well, see: https://go.dev/tour/methods/12.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment