Skip to content

proposal: Go 2: Ban typed nil interfaces through banning nil method receivers #27890

Closed
@JavierZunzunegui

Description

@JavierZunzunegui

Overview

Methods with pointer receivers have no restrictions on nil receivers. For example the below is valid:

type X struct{}
func (x *X) A() {...}
var x *X
x.A()

This extends to interfaces, creating the issue of typed nil interfaces. See typed-nils-in-go-2 for further context. I would like to see this situation removed in Go2 as typed nil interfaces are a source of bugs and offer no meaningful additional functionality.

Proposed Changes

I propose some changes to the Go2 language spec that would mean banning nil receivers, resulting in banning typed nil interfaces.

Make every method with a pointer receiver nil-pointer panic (at the beginning of the method) on a nil receiver. Implicitly that means if this == nil {panic NilPointer} is added at the beginning of every method. Note this is already implicitly added to most methods (those that use the receiver), just further down the method on the first use the receiver. Compared to the current specs, this would result in:

type X struct{}
func (x *X) A() {...}
var x *X
x.A() // this would now nil-panic (before any logic from A is executed)

Follow this up with another change, when assigning a (typed) pointer to an interface, check its value. If nil, set the interface value as the untyped nil. Note this means the specific type of the pointer unrecoverable. As an example:

type X struct{}
var x *X
i interface{}
i = x
isNil := i == nil  // currently isNil == false, I propose isNil == true
_, ok := i.(*X) // currently ok == true, I propose ok == false

This means if an interface is a typed pointer, it doesn't have to check if the pointer is nil or not, it is non-nil by design, hence the typed nil interface problem is gone.

Other considerations

I imagine others gophers will identify many others issues, these are some I am aware and would like to share upfront:

Optional nil receivers

Methods optionally allowing nil receivers are no longer allowed:

func (x *X) ... {
  if x == nil {
    return nil
  }
  // do something else

Instead a pointer type would be required, type XPtr struct {*X}, which would do the nil check.

Type assertions in nil types

By design, type assertions from nil pointer types are no longer supported. I don't think it is a common (or advisable) pattern, but it will still break the existing libraries using it. Where genuinely needed a pointer type could be used to wrap the pointer: type XPtr struct {*X}.

Methods not using the receiver

Methods do not have to use the receiver. The current spec does not distinguish between the two ways these may be defined (with/without named receiver): func (x *X) ... or func (*X) .... I suggest using this ambiguity and defining them differently in Go2, while the named receiver is just a normal method (and can't be called on a nil typed pointer) the unnamed one may be called on typed nil and does not have the implicit nil check. Note however that because interfaces are never typed nils, once called from an interface it will always be called with a non-nil receiver.

Methods as functions

Consider the below situation:

type X struct {}
func (x *X) A() {...}
var x *X
f := x.A // what to do with this? I suggest panic here

A method on a nil typed pointer is being assigned to a function variable. Calling that function will always panic, making it useless. For this reason I think it is better to change the language spec to have this panic on the assignment.

Compiler considerations

Note these considerations are not affecting the language spec, but I think they may make this changes more appealing.

This proposal means interfaces holding pointers do not have to check if the pointer they hold is nil valued. But that is largely useless if the first thing all methods do is check if the value of the pointer is nil. It would be much better if the nil check where performed by the caller (and still NilPointer panic if nil), improving performance by reducing the number of nil checks (among other things, methods would not need to make that check when calling other methods on themselves).

Also related, when assigning a pointer to an interface the value has to be checked for nil (which is not required currently). While this makes assigning to an interface more expensive, it is likely the compiler can optimize away many of these checks as the nil-ness of the pointer may be known at compile time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions