Skip to content

proposal: Go 2: allow pointer types to be receiver base types in method declarations #48592

Closed
@leighmcculloch

Description

@leighmcculloch

What version of Go are you using (go version)?

$ go version
go version go1.17.1 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN="/Users/leighmcculloch/.local/bin"
GOCACHE="/Users/leighmcculloch/Library/Caches/go-build"
GOENV="/Users/leighmcculloch/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/leighmcculloch/.local/gopath/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/leighmcculloch/.local/gopath"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/Users/leighmcculloch/.local/bin/go/latest"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/leighmcculloch/.local/bin/go/latest/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.17.1"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="0"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/q5/xb4dl4bs3cs32khlg0ryy0vw0000gp/T/go-build1564949535=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Define a type that is a pointer to another type, and define methods for that type, so that type can be marshaled differently to include information about the optional quality of the pointer.

type S struct {
	// ...
}

func (s S) MarshalBinary() ([]byte, error) {
  // ...
}

func (s *S) UnmarshalBinary(inp []byte) error {
  // ...
}

type P *S

func (p P) MarshalBinary() ([]byte, error) {
  // ...
}

func (p *P) UnmarshalBinary(inp []byte) error {
  // ...
}

Example: https://github.com/stellar/go/blob/e24fae34b9166cc61bb9e6c4645eaabff844768b/xdr/xdr_generated.go#L1620

What did you expect to see?

I expected go build to suceed at compiling.

What did you see instead?

invalid receiver type P (P is a pointer type)

Discussion

This behavior appears to be consistent with the specification that states:

A receiver base type cannot be a pointer...

There is also prior discussion on this topic on the golang-nuts mailing list that points out why this behavior is this way.

Why is it not allowed to define pointer types as named types with methods?

Because in a case like this:

type I int
type P *I
func (i I) Get() int { return int(i) }
func (p P) Get() int { return int(*p) }
var v I
var x = (&v).Get()

it would be unclear whether the Get method in the last line would be
I.Get or P.Get. We could define a rule for it, but that would become
another thing that people would have to know.

Ref: https://groups.google.com/g/golang-nuts/c/qf76N-uDcHA

However, after viewing code running in Go 1.17.1 today the reason that was given for why these types cannot be receivers is less clear. The prior conversation suggests we don't have a rule for what type &v is, however &v is possible today and so we can write code that tells us what that rule is.

For example, the output of the following code tells us what the rule is:

type I int

type P *I

func main() {
	i := I(0)
	fmt.Printf("i = %T\n", i)
	fmt.Printf("&i = %T\n", &i)
	p := P(&i)
	fmt.Printf("p = %T\n", p)
	fmt.Printf("&p = %T\n", &p)
	fmt.Printf("*p = %T\n", *p)
}
i = main.I
&i = *main.I
p = main.P
&p = *main.P
*p = main.I

Ref: https://play.golang.org/p/YdYlHDub0eg

The output of the above code is pretty clear that &i is always *I and never P, therefore in the example on the mailing list &v is always *I, therefore (&v).Get() should also always call I.Get since according to the specification:

The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

Being able to define types that are pointer types would make defining parsers for nested optional values simpler, because it would remove the need to wrap those nested types in a struct.

The proposal is support for pointer types to be a receiver base type in method declarations.

The specification would be changed from:

A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method.

To:

A receiver base type cannot an interface type and it must be defined in the same package as the method.

Proposal template
  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    Experienced

  • What other languages do you have experience with?
    Java, Ruby, C#, C, JavaScript

  • Would this change make Go easier or harder to learn, and why?
    Neither.

  • Has this idea, or one like it, been proposed before? If so, how does this proposal differ?
    See discussion on the mailing list above.

  • Who does this proposal help, and why?
    People building mashalers with nested optional types.

  • What is the proposed change?
    See above.

    • Please describe as precisely as possible the change to the language.
      See above.

    • What would change in the language spec?
      The definition

    • Please also describe the change informally, as in a class teaching Go.
      See above.

  • Is this change backward compatible?
    Yes

  • Show example code before and after the change.
    See above.

  • What is the cost of this proposal? (Every language change has a cost).

    • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
      Likely none.
    • What is the compile time cost?
      Insignificant.
    • What is the run time cost?
      Insignificant.
  • Can you describe a possible implementation?
    See above.

    • Do you have a prototype? (This is not required.)
      No. But I can take a stab at contributing it.
  • How would the language spec change?
    See above.

  • Orthogonality: how does this change interact or overlap with existing features?
    Not sure.

  • Is the goal of this change a performance improvement?
    No.

    • If so, what quantifiable improvement should we expect?
    • How would we measure it?
  • Does this affect error handling?
    No.

  • Is this about generics?
    No.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions