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 *Node
s 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. :)