4

Assumptions: In go, all function arguments are passed by value. To get pass-by-reference semantics/performance, go programmers pass values by pointer. Go will still make a copy of these arguments, but its making a copy of a pointer which is sometimes more memory efficient than making a copy of the actual parameter.

Question: What is going on when you pass an interface? i.e., in a program like this

package main
import "fmt"

type Messages struct {
    hello string
}

func main() {
  sayHelloOne(Messages{"hello world"});
  sayHelloTwo(&Messages{"hello world"});
  sayHelloThree(Messages{"hello world"});
}


//go makes a copy of the struct
func sayHelloOne(messages Messages) {
  fmt.Println(messages.hello)
}

//go makes a *pointer* to the struct
func sayHelloTwo(messages *Messages) {
  fmt.Println(messages.hello)
}

//go --- ???
func sayHelloThree(messages interface{}) {
  fmt.Println(messages.(Messages).hello)
}

What happens with the argument when a programmer calls the sayHelloThree function? Is messages being copied? Or is it a pointer to messages that's copied? Or is there some weird-to-me deferring going on until messages is cast?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • 1
    See related question: [How can a slice contain itself?](https://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself/36078970#36078970) – icza Mar 07 '18 at 17:05

1 Answers1

5

The interface value is copied. The interface value includes an underlying type descriptor and the underlying value, which may be any type (pointer or otherwise) which satisfies the interface. Being "wrapped" in an interface has no impact on these semantics. You can see this in your own quoted code: you have to assert the type to a Messages value, not a *Messages pointer. If, on the other hand, you passed a *Messages in as the parameter, that's what you'd get inside the function.

This is easily demonstrated experimentally:

m := Messages{"hello world"}
var mi interface{}
mi = m
m.hello = "wait what?"
fmt.Println(mi.(Messages).hello)
// hello world

Working playground example: https://play.golang.org/p/lnVzr79eUZF

Also, be careful with generalizations like "making a copy of a pointer which is much more memory efficient than making a copy of the actual parameter" - this is not universally true, and the change in semantics is often more important than the change in resource usage patterns. For example, a pointer is obviously less efficient when the value is smaller than an address on the operating architecture. A pointer can also force the value onto the heap rather than the stack, which - regardless of its impact on memory usage - increases GC pressure, because the GC gets to ignore values on the stack.

Adrian
  • 42,911
  • 6
  • 107
  • 99
  • Re: generalizations -- I've edited the post to say "often more memory efficient". Thank you for the reminder, and for the answer – Alana Storm Mar 07 '18 at 17:06
  • Thank you for this excellent answer. One caveat/quibble -- I can't seem to pass a *Message in as a parameter. This `sayHelloThree(&Messages{"hello world"});` . ends up producing a `panic: interface conversion: interface {} is *main.Messages, not main.Messages` error. – Alana Storm Mar 07 '18 at 17:11
  • Right, because you're asserting the type to `Messages`. The code inside the function is making an assumption about the underlying type of the interface value. That means if you change the type you pass in, you have to change the assertion. – Adrian Mar 07 '18 at 17:12
  • *palmsface*. Right. Thank you again. – Alana Storm Mar 07 '18 at 17:15
  • I'm not sure I'd even say it's "often" more memory efficient - updated my answer with more details w/r/t to this line of thinking. It's *sometimes* more memory efficient, certainly, but I think "it's complicated" is the best generalization that can be made there :) – Adrian Mar 07 '18 at 17:15
  • 1
    At some version of Go, a long while back (1.4?) pointers and GC use escape analysis, and pointers that don't escape function scope (after inlining, so function scope can be bigger than you think) go on the stack, not the heap. – Zan Lynx Mar 07 '18 at 17:24
  • 1
    True, escape analysis is applied to pointers... but that's getting deep into the weeds. If it's that performance-sensitive you should be poring over the detailed escape analysis of your builds anyway, so general advice no longer matters. – Adrian Mar 07 '18 at 17:30