user776942
user776942

Reputation:

Why can't I access this field in an interface?

I am trying to understand interfaces better and am not understanding why s has no field Width. My example is here:

package main

import "fmt"

type shapes interface {
    setWidth(float64)
}

type rect struct {
    Width float64
}

func (r *rect) setWidth(w float64) {
    r.Width = w
}

var allShapes = map[string]shapes{
    "rect": &rect{},
}

func main() {
    r := &rect{}
    r.setWidth(5)
    fmt.Println(r.Width)  // this works
    for _, s := range allShapes {
        s.setWidth(7)
        fmt.Println(s.Width) // why not???
    }
}

Why does r have Width but s doesn't? The exact error I get is:

s.Width undefined (type shapes has no field or method Width)

Upvotes: 9

Views: 3133

Answers (2)

Pandemonium
Pandemonium

Reputation: 8390

shapes interface is what *rect implements, but it is not a concrete type *rect. It is, like any interface, a set of methods allowing any type satisfying it to pass, like giving a temporary visitor sticker to it to go up the building.

For instance, if there is a monkey (or for what it's worth, a dolphin) who can act and do everything a human can, in Go's building, he can pass the guard and go up the elevator. However, that doesn't make him genetically human.

Go is statically-typed, meaning even two types with the same underlying type cannot be dynamically converted or coerced to one another without a type assertion or consciously converting the type.

var a int
type myInt int
var b myInt

a = 2
b = 3
b = a         // Error! cannot use a (type int) as type myInt in assignment.
b = myInt(a)  // This is ok.

Imagine with me for a second this situation:

type MyInt int
type YourInt int

type EveryInt interface {
        addableByInt(a int) bool
}

func (i MyInt) addableByInt(a int) bool {
    // whatever logic doesn't matter
    return true
}


func (i YourInt) addableByInt(a int) bool {
    // whatever logic doesn't matter
    return true
}

func main() {
    // Two guys want to pass as an int
    b := MyInt(7)
    c := YourInt(2)

    // Do everything an `EveryInt` requires
    // and disguise as one 
    bi := EveryInt(b)
    ci := EveryInt(c)

    // Hey, look we're the same! That's the closest
    // we can get to being an int!
    bi = ci          // This is ok, we are EveryInt brotherhood
    fmt.Println(bi)  // bi is now 2

    // Now a real int comes along saying
    // "Hey, you two look like one of us!"
    var i int
    i = bi           // Oops! bi has been made

    // ci runs away at this point

}

Now back to your scenerio--imagine a *circle comes along implementing shapes:

type circle struct {
        Radius float64
}

func (c *circle) setWidth(w float64) {
        c.Radius = w
}

*circle is totally passable as shapes but it does not have Width property because it is not a *rect. An interface cannot access an underlying type's property directly, but can only do so through implemented method set. In order to access a property, a type assertion is need on the interface so that instance becomes a concrete type:

var r *rect

// Verify `s` is in fact a `*rect` under the hood
if r, ok := s.(*rect); ok {
        fmt.Println(r.Width)
}

This is at the core of why a statically-typed language like Go is always faster than dynamically-typed counterparts, which will almost always use some kind of reflection to handle type coercion dynamically for you.

Upvotes: 7

matt.s
matt.s

Reputation: 1736

s is a an implementer of the shapes interface, but in the for loop not typed as a rect. If you do a type assertion to force s be of the concrete type like so:

s.(*rect).Width

You will get what you want.

You need to be careful about mixing concrete types and interfaces like that though.

Upvotes: 3

Related Questions