0

if i had an instance of the following struct

type Node struct {
    id          string 
    name        string  
    address     string
    conn        net.Conn
    enc         json.Encoder
    dec         json.Decoder
    in          chan *Command
    out         chan *Command
    clients     map[string]ClientNodesContainer
}

i am failing to understand when i should send a struct by reference and when should i send it by value(considering that i do not want to make any changes to that instance), is there a rule of thumb that makes it easier to decide?

all i could find is send a struct by value when its small or inexpensive to copy, but does small really mean smaller than 64bit address for example?

would be glad if someone can point some more obvious rules

user1590636
  • 1,174
  • 6
  • 26
  • 56
  • 1
    What I could puzzle out is at http://stackoverflow.com/questions/23542989/best-practice-returning-structs-in-go/23551970#23551970 . As to what "large" is, it's definitely more than 24 bytes (the size of a slice header on x64, something we often pass by value); the code review comments use the intuitive rule-of-thumb "would it feel awkward to pass all the fields as arguments?"; I'd say, roughly, if you're under 64 bytes (the size of a cache line on x64) and pass-by-value feels OK it's not worth sweating the performance (unless it's perf-critical code, in which case measure it). – twotwotwo Aug 17 '14 at 23:46

3 Answers3

11

The rule is very simple:

There is no concept of "pass/send by reference" in Go, all you can do is pass by value.

Regarding the question whether to pass the value of your struct or a pointer to your struct (this is not call by reference!):

  1. If you want to modify the value inside the function or method: Pass a pointer.
  2. If you do not want to modify the value:
    1. If your struct is large: Use pointer.
    2. Otherwise: It just doesn't matter.

All this thinking about how much a copy costs you is wasted time. Copies are cheap, even for medium sized structs. Passing a pointer might be a suitable optimization after profiling.

Your struct is not large. A large struct contains fields like wholeWorldBuf [1000000]uint64. Tiny structs like yours might or might not benefit from passing a pointer and anybody who gives advice which one is better is lying: It all depends on your code and call patterns.

If you run out of sensible options and profiling shows that time is spent copying your structs: Experiment with pointers.

Volker
  • 40,468
  • 7
  • 81
  • 87
  • 1
    The definition of "large" also comes down to the platform you're running on. If your platform cannot copy your struct in a few cycles then its too large and should probably be passed by reference (or you can just weather the performance hit). – Simon Whitehead Aug 18 '14 at 00:34
  • "Pass values more" is usually the right direction to push new Go folks (or else you get a lot of `*[]byte` and similar silliness) but I wouldn't make it a nearly-absolute principle like this; pointers can make sense for something smaller than an 8M buffer. The code review comments section on pointer receivers vs. value receivers has an interesting sentence: "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." – twotwotwo Aug 18 '14 at 07:26
2

The principle of "usually pass values for small structs you don't intend to mutate" I agree with, but this struct, right now, is 688 bytes on x64, most of those in the embedded json structs. The breakdown is:

  • 16*4=64 for the three strings (pointer/len pairs) and the net.Conn (an interface value)
  • 208 for the embedded json.Encoder
  • 392 for the embedded json.Decoder
  • 8*3=24 for the three chan/map values (must be pointers)

Here's the code I used to get that, though you need to save it locally to run it because it uses unsafe.Sizeof.

You could embed *Encoder/*Decoder instead of pointers, leaving you at 104 bytes. I think it's reasonable to keep as-is and pass *Nodes around, though.

The Go code review comments on receiver type say "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." There is room for differences of opinion here, but for me, nine values, some multiple words, "feels large" even before getting precise numbers.

In the "Pass Values" section the review comments doc says "This advice does not apply to large structs, or even small structs that might grow." It doesn't say that the same standard for "large" applies to normal parameters as to receivers, but I think that's a reasonable approach.

Part of the subtlety of determining largeness, alluded to in the code review doc, is that many things in Go are internally pointers or small, reference-containing structs: slices (but not fixed-size arrays), strings, interface values, function values, channels, maps. Your struct may own a []byte pointing to a 64KB buffer, or a big struct via an interface, but still be cheap to copy. I wrote some about this in another answer, but nothing I said there prevents one from having to make some less-than-scientific judgement calls.

It's interesting to see what standard library methods do. bytes.Replace has ten words' worth of args, so that's at least sometimes OK to copy onto the stack. On the other hand go/ast tends to pass around references (either * pointers or interface values) even for non-mutating methods on its not-huge structs. Conclusion: it can be hard to reduce it to a couple simple rules. :)

Community
  • 1
  • 1
twotwotwo
  • 28,310
  • 8
  • 69
  • 56
-1

Most of the time you should use passing by reference. Like:

func (n *Node) exampleFunc() {
    ...
}

Only situation when you would like to use passing instance by value is when you would like to be sure that your instance is safe from changes.

Piotr Kowalczuk
  • 400
  • 5
  • 19