3

Starting with code like:

type Foo struct {
    V bool
}

func (f Foo) bar() bool {
    return f.V
}

Is it allowed to change to func (f *Foo) bar() bool without incrementing the major version number? That is, assuming you know there are no thread safety issues with your type. If so, the reverse change is allowed too, correct?

Any code that called the function regardless of whether the variable was a value or a pointer would continue to compile and work as expected after both changes, as far as I can tell.

Fsmv
  • 1,106
  • 2
  • 12
  • 28
  • 1
    Does [this](https://stackoverflow.com/questions/33587227/method-sets-pointer-vs-value-receiver) answer your question?. [Here](https://go.dev/play/p/QNnQ2Kgqy2C) is an example that breaks with the change. – Brits Jul 21 '23 at 05:05
  • Thanks! I think that does answer it, it is not compatible to make this change. I suppose point 4 in the answer to that question also means that it would change which type (pointer or value) satisfies any interfaces it has the methods for, so I suppose that makes the reverse change not compatible either if it satisfies any interfaces (and you can't guarantee it doesn't). – Fsmv Jul 21 '23 at 06:18
  • Fortunately I benchmarked it and it makes no difference in speed whether my type is a pointer or value anyway – Fsmv Jul 21 '23 at 06:19
  • @Brits could you add that an an answer so I can accept it? – Fsmv Jul 22 '23 at 00:18

2 Answers2

1

If for a type Foo you have a method with value receiver:

func (f Foo) Method() {...}

Then this method is defined for both Foo and *Foo, and for both cases, the method receives a copy of the instance of Foo. If you change this to :

func (f *Foo) Method() {...}

then Foo.Method is only available for *Foo, and not for Foo. Thus, such a change may cause compile errors.

If you have a method declared with pointer receiver and now you change that to a value receiver, you declare the method for both Foo and *Foo, so you should not have compile errors. The semantics of the method will also change, because now Method will receive a copy of the instance of Foo even if that instance is addressable, or it is a pointer.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
  • I tested it and it compiles to use a pointer variable with a value receiver: https://go.dev/play/p/pKwO7oA39w7 I thought it wouldn't work before I checked it too! – Fsmv Jul 21 '23 at 03:17
1

As per the comments, this answer answer provides a good summary T vs *T receiver types. Following are a couple of examples where a change from func (f Foo) bar() bool to func (f *Foo) bar() bool would break existing code.

Example 1: T is not addressable (playground):

type Foo struct {
    V bool
}

func (f *Foo) bar() bool { // Change to `(f Foo)` and this will work
    return f.V
}

func x() Foo {
    return Foo{true}
}

func main() {
    fmt.Printf("%v", x().bar())
}

Example 2: Interface (playground):

type Foo struct {
    V bool
}

func (f *Foo) bar() bool { // Change to `(f Foo)` and this will work
    return f.V
}

type Fooer interface {
    bar() bool
}

func main() {
    var x Fooer
    x = Foo{}
    fmt.Println(x.bar())
}
Brits
  • 14,829
  • 2
  • 18
  • 31