Value receiver vs. pointer receiver
Asked Answered
M

4

246

It is very unclear for me in which case I would want to use a value receiver instead of always using a pointer receiver.

To recap from the docs:

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

The docs also say "For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear."

First point they docs say a value receiver is "very cheap", but the question is whether it is cheaper than a pointer receiver. So I made a small benchmark (code on gist) which showed me, that pointer receiver is faster even for a struct that has only one string field. These are the results:

// Struct one empty string property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  500000000                3.62 ns/op


// Struct one zero int property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  2000000000               0.36 ns/op

(Edit: Please note that second point became invalid in newer go versions, see comments.)

Second point the docs say that a value receiver it is "efficient and clear" which is more a matter of taste, isn't it? Personally I prefer consistency by using the same thing everywhere. Efficiency in what sense? Performance wise it seems pointer are almost always more efficient. Few test-runs with one int property showed minimal advantage of Value receiver (range of 0.01-0.1 ns/op)

Can someone tell me a case where a value receiver clearly makes more sense than a pointer receiver? Or am I doing something wrong in the benchmark? Did I overlook other factors?

Mackenziemackerel answered 5/1, 2015 at 7:54 Comment(2)
I ran similar benchmarks with a single string field and also with two fields: string and int fields. I got faster results from the value receiver. BenchmarkChangePointerReceiver-4 10000000000 0.99 ns/op BenchmarkChangeItValueReceiver-4 10000000000 0.33 ns/op This is using Go 1.8. I wonder if there were compiler optimizations made since you last ran the benchmarks. See gist for more details.Gaspard
You're right. Running my original benchmark using Go1.9, I get different results now as well. Pointer Receiver 0.60 ns/op, Value receiver 0.38 ns/opMackenziemackerel
V
251

Note that the FAQ does mention consistency

Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.

As mentioned in this thread:

The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers

Which is not true, as commented by Sart Simha

Both value receiver and pointer receiver methods can be invoked on a correctly-typed pointer or non-pointer.

Regardless of what the method is called on, within the method body the identifier of the receiver refers to a by-copy value when a value receiver is used, and a pointer when a pointer receiver is used: example.

Now:

Can someone tell me a case where a value receiver clearly makes more sense then a pointer receiver?

The Code Review comment can help:

  • If the receiver is a map, func or chan, don't use a pointer to it.
  • If the receiver is a slice and the method doesn't reslice or reallocate the slice, don't use a pointer to it.
  • If the method needs to mutate the receiver, the receiver must be a pointer.
  • If the receiver is a struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying.
  • If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it's equivalent to passing all its elements as arguments to the method. If that feels too large, it's also too large for the receiver.
  • Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy of the receiver when the method is invoked, so outside updates will not be applied to this receiver. If changes must be visible in the original receiver, the receiver must be a pointer.
  • If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader.
  • If the receiver is a small array or struct that is naturally a value type (for instance, something like the time.Time type), with no mutable fields and no pointers, or is just a simple basic type such as int or string, a value receiver makes sense.
    A value receiver can reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. (The compiler tries to be smart about avoiding this allocation, but it can't always succeed.) Don't choose a value receiver type for this reason without profiling first.
  • Finally, when in doubt, use a pointer receiver.

Note on "If the receiver is a slice and the method doesn't reslice or reallocate the slice, don't use a pointer to it."

The statement is suggesting that if you have a method that reslices or reallocates the slice, then you should use a pointer receiver.

In other words, if you modify the slice within the method, such as appending elements or changing the length/capacity of the slice, it's recommended to use a pointer receiver.

In the case of implementing deletion and insertion methods for a slice type, you will likely be modifying the slice (changing its length, appending or removing elements). Therefore, you should use a pointer receiver for these methods.

Example (playground):

package main

import "fmt"

type MySlice []int

func (s *MySlice) Insert(index int, value int) {
    // Insert value at index and shift elements
    *s = append((*s)[:index], append([]int{value}, (*s)[index:]...)...)
}

func (s *MySlice) Delete(index int) {
    // Remove the element at index and shift elements
    *s = append((*s)[:index], (*s)[index+1:]...)
}

func main() {
    s := MySlice{1, 2, 3, 4, 5}
    s.Insert(2, 42)
    fmt.Println(s) // Output: [1 2 42 3 4 5]

    s.Delete(2)
    fmt.Println(s) // Output: [1 2 3 4 5]
}

In this example, the Insert and Delete methods are modifying the slice by appending and removing elements.
As a result, a pointer receiver is used to ensure the modifications are visible outside the method.


The part in bold is found for instance in net/http/server.go#Write():

// Write writes the headers described in h to w.
//
// This method has a value receiver, despite the somewhat large size
// of h, because it prevents an allocation. The escape analysis isn't
// smart enough to realize this function doesn't mutate h.
func (h extraHeader) Write(w *bufio.Writer) {
...
}

Note: irbull points out in the comments a warning about interface methods:

Following the advice that the receiver type should be consistent, if you have a pointer receiver, then your (p *type) String() string method should also use a pointer receiver.

But this does not implement the Stringer interface, unless the caller of your API also uses pointer to your type, which might be a usability problem of your API.

I don't know if consistency beats usability here.


points out to:

you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which.
Both will work, and the syntax is the same.

However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won't be valid.

Calling value receiver methods through interfaces always creates extra copies of your values.

Interface values are fundamentally pointers, while your value receiver methods require values; ergo every call requires Go to create a new copy of the value, call your method with it, and then throw the value away.
There is no way to avoid this as long as you use value receiver methods and call them through interface values; it's a fundamental requirement of Go.

Concept of unaddressable values, which are the opposite of addressable values. The careful technical version is in the Go specification in Address operators, but the hand waving summary version is that most anonymous values are not addressable (one big exception is composite literals)

Velocity answered 5/1, 2015 at 8:10 Comment(15)
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers Not true, actually. Both value receiver and pointer receiver methods can be invoked on a correctly-typed pointer or non-pointer. Regardless of what the method is called on, within the method body the identifier of the receiver refers to a by-copy value when a value receiver is used, and a pointer when a pointer receiver is used: See play.golang.org/p/3WHGaAbURMIntersex
There is a great explanation here "If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m(). "Golem
@Golem Yes: that is discussed at https://mcmap.net/q/88470/-go-method-call-shorthand-spec-applicability/6309Velocity
Great answer but I strongly dissagree with this point: "as it will make the intention more clear", NOPE, a clean API, X as argument and Y as a return value is a clear intention. Passing a Struct by pointer and spending time on carefully reading the code to check what all attributes get modified is far from clear, maintainable.Uriisa
@HartSimha I think the post above is pointing to fact that pointer receiver methods are not in "method set" for value types. In your linked playground, adding following line will result in compilation error: Int(5).increment_by_one_ptr(). Similarly, a trait that defines the method increment_by_one_ptr will not be satisfied with a value of type Int.Hanseatic
more info for @HartSimha and @Gaurav Agarwal 's comment: As with method calls, a reference to a non-interface method with a pointer receiver using an addressable value will automatically take the address of that value: t.Mp is equivalent to (&t).Mp.quote from method valuesBobette
I'm surprised nobody has talked about Interface methods here, as they can lead to very subtle bugs. Following the advice that the receiver type should be consistent, if you have a pointer receiver then your (p *type) String() string method should also use a pointer receiver, but this Does not implement the Stringer interface, unless the caller of your API also uses pointer to your type, which might be a usability problem of your API. I don't know if consistency beats usability here.River
@River Thank you. Very good point. I have included your comment in the answer for more visibility.Velocity
As @HartSimha observed, this answer contains some cargo cult rumours among the good information. An accurate (if terse) description of the situation can be found in this answer: https://mcmap.net/q/88469/-method-sets-pointer-vs-value-receiver A very useful investigation into "unaddressable" is here: utcc.utoronto.ca/~cks/space/blog/programming/… (particularly useful because the official documentation on this is not helpful. Hidden costs of value methods here: utcc.utoronto.ca/~cks/space/blog/programming/…Malaco
@Malaco Thank you for this feedback. Could you point out the cargo cult rumors, for me to edit the answer and remove them? That will help other readers.Velocity
I believe implementing json.Unmarshaler is another case where using pointer receiver cannot be realistically avoided. Without it, there's nothing to unmarshal into without everything being lost after returning. The opposite is true for json.Marshaler. If you do not use a value-receiver there, you won't be able to marshal non-pointer values of your type because json.Marshal() won't call the method.Bonaparte
@Bonaparte Good point. I see you have some experience in this: https://mcmap.net/q/88471/-why-is-the-receiver-value-nil-in-this-use-of-unmarshaljson/6309Velocity
@Velocity I need to implement these guys like once in a blue moon. Trips me up every time.Bonaparte
"if the method doesn't reslice or reallocate the slice, don't use a pointer to it." the double negation kills me sorry. Should I interpret as, if it calls append, or mySlice[i:], I probably need a pointer receiver? Say I want to implement deletion and insertion on the slice type.Grubby
@EricBurel Yes, using a pointer is recommended in that case. See my edited answer: I have added an all section on that sentence.Velocity
U
43

To add additionally to @VonC great, informative answer.

I am surprised no one really mentioned the maintainance cost once the project gets larger, old devs leave and new one comes. Go surely is a young language.

Generally speaking, I try to avoid pointers when I can but they do have their place and beauty.

I use pointers when:

  • working with large datasets
  • have a struct maintaining state, e.g. TokenCache,
    • I make sure ALL fields are PRIVATE, interaction is possible only via defined method receivers
    • I don't pass this function to any goroutine

E.g:

type TokenCache struct {
    cache map[string]map[string]bool
}

func (c *TokenCache) Add(contract string, token string, authorized bool) {
    tokens := c.cache[contract]
    if tokens == nil {
        tokens = make(map[string]bool)
    }

    tokens[token] = authorized
    c.cache[contract] = tokens
}

Reasons why I avoid pointers:

  • pointers are not concurrently safe (the whole point of GoLang)
  • once pointer receiver, always pointer receiver (for all Struct's methods for consistency)
  • mutexes are surely more expensive, slower and harder to maintain comparing to the "value copy cost"
  • speaking of "value copy cost", is that really an issue? Premature optimization is root to all evil, you can always add pointers later
  • it directly, conciously forces me to design small Structs
  • pointers can be mostly avoided by designing pure functions with clear intention and obvious I/O
  • garbage collection is harder with pointers I believe
  • easier to argue about encapsulation, responsibilities
  • keep it simple, stupid (yes, pointers can be tricky because you never know the next project's dev)
  • unit testing is like walking through pink garden (slovak only expression?), means easy
  • no NIL if conditions (NIL can be passed where a pointer was expected)

My rule of thumb, write as many encapsulated methods as possible such as:

package rsa

// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
    return []byte("secret text"), nil
}

cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) 

UPDATE:

This question inspired me to research the topic more and write a blog post about it https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701

Uriisa answered 21/8, 2018 at 21:19 Comment(6)
I like 99% of what you say here and strongly agree with it. That said I'm wondering if your example is the best way to illustrate your point. Isn't TokenCache essentially a map (from @Velocity - "if the receiver is a map, func or chan, don't use a pointer to it"). Since maps are reference types what do you gain by making "Add()" a pointer receiver? Any copies of TokenCache will reference the same map. See this Go playground - play.golang.com/p/Xda1rsGwvhqWeedy
Glad we are aligned. Great point. Actually, I think I have used pointer in this example because I copied it from a project where the TokenCache is handling more stuff than just that map. And If I use a pointer in one method, I use it in all of them. Do you suggest removing the pointer from this particular SO example?Uriisa
LOL, copy/paste strikes again! 😉 IMO you can leave it as is since it illustrates a trap that’s easy to fall into, or you could replace the map with something(s) that demonstrate state and/or a large data structure.Weedy
Well, I am sure they will read the comments... PS: Rich, your arguments seem reasonable, add me on LinkedIn (link in my profile) happy to connect.Uriisa
@Lukas: A very side note: The Slovak 'walking through ROSE (not pink!) garden' has a quite similar counterpart in English: 'bed of roses'. So 'Unit testing is s bed of roses' :-)Palmore
"pointers are not concurrently safe (the whole point of GoLang)" => I have lost one hour because i have used goroutine on pointer receiver function ...Shine
A
3

It is a question of semantics. Imagine you write a function taking two numbers as arguments. You don't want to suddenly find out that either of these numbers got mutated by the calling function. If you pass them as pointers that is possible. Lots of things should act just like numbers. Things like points, 2D vectors, dates, rectangles, circles etc. These things don't have identity. Two circle at the same position and with the same radius should not be distinguished from each other. They are value types.

But something like a database connection or a file handle, a button in the GUI is something where identity matters. In these cases you want a pointer to the object.

When something is inherently a value type such as a rectangle or point, it is really preferable to be able to pass them without using pointers. Why? Because it means you are certain to avoid mutating the object. It clarifies semantics and intent to reader of your code. It is clear that the function receiving the object cannot and will not mutate the object.

Allay answered 27/2, 2021 at 1:52 Comment(0)
E
1

Pointer receiver vs Value receiver vs mixing

You shouldn't mix value and pointer receiver in object because if you declare once value receiver which means that Object can be only value instance. And if somebody declare instance of your Object as nil this get panic and you can't check this in you method because your method receive value.

Example:

package main

import (
    "fmt"
)

type ObjV struct {
    a string
}

func (o ObjV) String() string {
    return o.a
}

type ObjP struct {
    a string
}

func (o *ObjP) String() string {
    if o == nil {
        return "" // <- you can control nil instance with pointer reciver
    }
    return o.a
}

func main() {
    var op *ObjP
    a := op.String()
    fmt.Println("op is: ", a)
    var ovp *ObjV
    a = ovp.String() //<-- panic here
    fmt.Println("ovp is: ", a)
    var ov ObjV //<-- another developer shoud know that you object can be only as value
    a = ov.String()
    fmt.Println("ov is: ", a)
}

According this you should have all methods with value receiver or all with pointer receivers to prevent confusion confusion among users of your Object.

Eris answered 9/2 at 11:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.