8

Edit: This is not the right way to use interfaces in Go. The purpose of this question is for me to understand how empty interfaces work in Go.

If all types in Go implement interface{} (empty interface), why can't I access the name field in the Cat and Dog structs? How can I get access to the name field of each struct through the function sayHi()?

package main

import (
    "fmt"
)

func sayHi(i interface{}) {

    fmt.Println(i, "says hello")

    // Not understanding this error message
    fmt.Println(i.name) //  i.name undefined (type interface {} is interface with no methods)
} 

type Dog struct{
    name string
}
type Cat struct{
    name string
}

func main() {
    d := Dog{"Sparky"}
    c := Cat{"Garfield"}

    sayHi(d) // {Sparky} says hello
    sayHi(c) // {Garfield} says hello
}
4

4 回答 4

13

An interface{} is a method set, not a field set. A type implements an interface if it's methods include the methods of that interface. Since empty interface doesn't have any methods, all types implement it.

If you need to access a field, you have to get the original type:

name, ok:=i.(Dog).name

This will recover the name if i is a Dog.

Alternatively, implement a getName() function for both Dog and Cat, and then they will both implement the following interface:

type NamedType interface {
   getName() string
}

Then you can rewrite your function as:

func sayHi(i NamedType) {
   fmt.Println(i.getName()) 
}
于 2020-01-29T23:12:45.323 回答
7

You can't do that because interface values don't do that.

What interface values do—<em>regardless of the interface type itself; it doesn't matter if the interface type is empty or not—is that they hold two things:

  • the concrete type of some value (or no type); and
  • the value of that concrete type (or no value).

So if some variable v or expression e has type I where I is an interface type, then you can, with some syntax, inspect either or both of these two "fields". They're not struct fields so you can't just use v.type, but you can do this:

switch v.(type) {
case int: // the value in v has type int
case *float64: // the value in v has type float64
// etc
}

The .(type) in a switch means let me look at the type field.

Getting the actual value is harder, because Go more or less requires that you check the type first. In your case, you know that i holds either a Dog or a Cat, so you can write:

var name string
switch i.(type) {
case Dog: name = i.(Dog).name
case Cat: name = i.(Cat).name
default: panic("whatever 'i' is, it is not a Dog or Cat")
}
fmt.Println(name)

This is pretty clumsy, and there are lots of ways to make it less clumsy, but that's always the first step: figure out what the type is.

Well, sometimes there's a step before the first step: figure out whether the variable has anything at all in it. You do this with:

if i == nil {
    ...
}

Note, however, that if i has some typed value in it, and the type can hold nil pointers, the value part of i can be nil and yet i == nil will be false. That's because i does have a type in it.

var i interface{}
var p *int
if i == nil {
    fmt.Println("i is initially nil")
}
if p == nil {
    fmt.Println("p is nil")
}
i = p
if i != nil {
    fmt.Printf("i is now not nil, even though i.(*int) is %v\n", i.(*int))
}

(try this on the Go playground).

This usually isn't the right way to use interface

Most often—there are exceptions—we don't even try to look at the type of some interface. Instead, we define an interface that provides methods—functions we can call—that do something we need done. See Burak Serdar's answer in which the interface type has a getName method. Then, instead of trying to figure out which of some limited set of types someone gave us, we just say:

name := i.getName()

to invoke the getName method on the underlying concrete value. If i holds a Dog, that calls func (Dog) getName() string, which you'll need to define. If i holds a Cat, it calls func (Cat) getName() string. If you decide to add to your collection a type named Bird, you can define func (Bird) getName() string, and so on.

(Usually, the methods would be exported too: GetName, rather than getName.)

于 2020-01-29T23:13:12.370 回答
4

Like you say, interface{} is an empty interface. How can you assume that something "empty" has a name field in it (fmt.Println(i.name))? You can't. In fact, go doesn't support fields in interfaces, only methods.

What you can do (and there are, of course, many solutions), is to define an interface (let's call it Pet) which has a method returning the pet's name:

type Pet interface {
    getName() string
}

Then you can receive this interface (its object) in the sayHi function and use it to print the pet's name:

func sayHi(i Pet) {
    fmt.Println(i.getName())
}

Now, in order to be able to pass Dog or Cat to sayHi(), both of these structs have to implement the interface. So, define the getName() methods for them:

func (d Dog) getName() string {
    return d.name
}

func (c Cat) getName() string {
    return c.name
}

And that's it.

于 2020-01-29T23:13:53.893 回答
0

It can also be implemented using switch type as below-

package main

import (
    "fmt"
)

func sayHi(i interface{}) {

    fmt.Println(i, "says hello")

    switch x := i.(type) {
    case Dog:
        fmt.Println(x.name)
    case Cat:
        fmt.Println(x.name)
    }

}

type Dog struct {
    name string
}
type Cat struct {
    name string
}

func main() {
    d := Dog{"Sparky"}
    c := Cat{"Garfield"}

    sayHi(d) // {Sparky} says hello
    sayHi(c) // {Garfield} says hello
}

Playground link - https://go.dev/play/p/p4s3_jmmbGJ

于 2022-01-03T11:37:31.327 回答