Erik Kaplun
Erik Kaplun

Reputation: 38217

How exactly are interface variables implemented in Go?

In the below code snippet, I'd like to understand what exactly gets stored in iPerson when its contents are still uninitialized: just a value of 0-bytes? Or is it actually a pointer under the hood (and also initialized to 0-bytes of course)? In any case, what exactly happens at iPerson = person?

If iPerson = person makes a copy of person, what happens then when an object implementing IPerson but with a different size/memory footprint gets assigned to iPerson? I understand iPerson is a variable stored on the stack, so its size must be fixed. Does that mean that the heap is actually used under the hood, so iPerson is actually implemented as a pointer, but assignments still copy the object, as demonstrated by the above code? Here's the code:

type Person struct{ name string }

type IPerson interface{}

func main() {
    var person Person = Person{"John"}
    var iPerson IPerson
    fmt.Println(person)  // => John
    fmt.Println(iPerson) // => <nil>  ...so looks like a pointer

    iPerson = person     //           ...this seems to be making a copy
    fmt.Println(iPerson) // => John

    person.name = "Mike"
    fmt.Println(person)  // => Mike
    fmt.Println(iPerson) // => John   ...so looks like it wasn't a pointer,
                         //           or at least something was definitely copied
}

(This question is the result of me having second thoughts on the precise factual correctness of my answer to why runtime error on io.WriterString?. So I decided to try to do some investigation to understand how is it exactly that interface variables and assignments to them work in Go.)

EDIT: after having received a few useful answers, I'm still puzzled with this:

iPerson = person
iPerson = &person

—both are legal. However, to me, this raises the question of why the compiler allows such weak typing to occur? One implication of the above is this:

iPerson = &person
var person2 = iPerson.(Person)  # panic: interface conversion: interface is *main.Person, not main.Person

whereas changing the first line fixes it:

iPerson = person
var person2 = iPerson.(Person)  # OK

...so it's not possible to determine statically whether iPerson holds a pointer or a value; and it seems that anything can assign either one to it at runtime with no errors raised. Why was such design decision made? What purpose does it serve? It definitely does not to fit within the "type safety" mindset.

Upvotes: 15

Views: 15611

Answers (3)

iant
iant

Reputation: 1229

You ask why both of

iPerson = person
iPerson = &person

are permitted. They are both permitted because both person and &person implement the IPerson interface. This is obvious, because IPerson is the empty interface--every value implements it.

It's true that you can't determine statically whether a value of IPerson holds a pointer or a value. So what? All you know about IPerson is that any object stored in a value of that type implements the list of methods in the interface. The assumption is that those methods are implemented correctly. Whether IPerson holds a value or a pointer is irrelevant to that.

For example, if the method is supposed to change something stored in the object, then that the method pretty much has to be a pointer method, in which case only a pointer value can be stored in the variable of interface type. But if none of the methods change something stored in the object, then they can all be value methods, and a non-pointer value can be stored in the variable.

Upvotes: 9

James Henstridge
James Henstridge

Reputation: 43899

When you execute the following line:

iPerson = person

You are storing a Person value in the interface variable. Since assignment to a struct performs a copy, yes your code is taking a copy. To retrieve the struct from inside the interface you'll need to take another copy:

p := iPerson.(Person)

so you'd rarely want to do this with mutable types. If you instead want to store a pointer to the struct in the interface variable, you need to do this explicitly:

iPerson = &person

As far as what goes on under the hood, you are right that interface variables allocate heap space to store values larger than a pointer, but this is usually not visible to the user.

Upvotes: 7

Erik Kaplun
Erik Kaplun

Reputation: 38217

So, looks like internally, the interface variable does hold a pointer to what was assigned to it. An excerpt from http://research.swtch.com/interfaces:

The second word in the interface value points at the actual data, in this case a copy of b. The assignment var s Stringer = b makes a copy of b rather than point at b for the same reason that var c uint64 = b makes a copy: if b later changes, s and c are supposed to have the original value, not the new one.

My question

[...] what happens then when an object implementing IPerson but with a different size/memory footprint gets assigned to iPerson?

...also gets answered in the article:

Values stored in interfaces might be arbitrarily large, but only one word is dedicated to holding the value in the interface structure, so the assignment allocates a chunk of memory on the heap and records the pointer in the one-word slot.

So yeah, a copy on the heap is made and a pointer to it assigned to the interface variable. But, apparently, to the programmer, the interface variable has the semantics of a value variable not a pointer variable.

(Thanks to Volker for providing the link; but also, the first part of his answer is factually plain wrong... So I don't know if I should downvote for the misleading information or upvote for the non-misleading and rather useful link (which also happens to contradict his own answer).)

Upvotes: 7

Related Questions