danze
danze

Reputation: 765

Types of Go struct methods that satisfy an interface

Given the following Go code example:

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var t2 *tourGuide = &tourGuide{"Smith"}
    t2.hello()   // Hello Smith
    t2.goodbye() // Goodbye Smith (same as (*t2).hello())

    // illegal: t1 is not assignable to g1 (why?)
    // var g1 greeter = t1

    var g2 greeter = t2
    g2.hello()   // Hello Smith
    g2.goodbye() // Goodbye Smith
}

I'm able to call the two methods of the struct tourGuide using either a variable of type tourGuide t1 or a pointer to tourGuide t2. In other words, I can call a method with T receiver using a variable of type T or *T. Similarly, I can call a method with *T receiver using a variable of type T (if T is addressable) or *T. I understand that the compiler handles the differences here (see my comments in the code).

However, things change when we are implementing interfaces. In the above code, a variable of type greeter interface is assignable from a pointer to tourGuide but not from a tourGuide.

Can anyone tell me why this is the case? Why am I able to call t1.hello() and t1.goodbye() but somehow t1 is not enough for the interface greeter?

Upvotes: 4

Views: 1322

Answers (3)

WPWoodJr
WPWoodJr

Reputation: 1241

My answer here explains why Go prevents you from taking the address of a value stored in an interface.

tl;dr its because a pointer to A which points to a value of type A in an interface would be invalidated when a value of different type B is subsequently stored in the interface.

Upvotes: 1

icza
icza

Reputation: 418745

If a method has a pointer receiver, only a pointer value can be used as the receiver value. So to call this method on some value, the value itself must be a pointer, or it must be possible to acquire its address (to be used as the receiver).

If you have a variable for example, it is addressable and thus it's possible to get its address and use that as the receiver. And the spec allows you to do this, this happens automatically.

Values wrapped in interfaces are not addressable. When an interface value is created, the value that is wrapped in the interface is copied. It is therefore not possible to take its address. Theoretically you could allow to take the address of the copy, but that would be the source of (even) more confusion than what benefit it would provide, as the address would point to a copy, and methods with pointer receiver could only modify the copy and not the original.

See this answer which details / proves that values are copied when the interface value is created: How can a slice contain itself?

Upvotes: 8

Daniel Robinson
Daniel Robinson

Reputation: 14898

if you have a pointer to a struct then go will allow you to access the properties on the struct and its functions which have value type receivers (as apposed to pointer receivers) without having to dereference your pointer, but this only works for one level of pointer, see your code below where I turn t2 into a pointer to a pointer to a tourguide, at this point I need to explicitly dereference it to make it back into a pointer to a tourguide. think of the first level of a pointer to a struct as being a special case that go allows you to use syntatic sugar to access the value types properties and functions to save you having to constantly manually dereference you variables.

package main

import "fmt"

type greeter interface {
    hello()
    goodbye()
}

type tourGuide struct {
    name string
}

func (t tourGuide) hello() {
    fmt.Println("Hello", t.name)
}

func (t *tourGuide) goodbye() {
    fmt.Println("Goodbye", t.name)
}

func main() {
    var t1 tourGuide = tourGuide{"James"}
    t1.hello()   // Hello James
    t1.goodbye() // Goodbye James (same as (&t1).goodbye())

    var tmpT2 *tourGuide = &tourGuide{"Smith"}
    var t2 **tourGuide = &tmpT2
    (*t2).hello()   // Hello Smith
    (*t2).goodbye() // Goodbye Smith (same as (*t2).hello())

    //illegal: t1 is not assignable to g1 (why?)
    //var g1 greeter = t1

    //now this is illegal too
    //var g2 greeter = t2

    var g3 greeter = (*t2)
    g3.hello()   // Hello Smith
    g3.goodbye() // Goodbye Smith
}

Upvotes: 3

Related Questions