Home > Software engineering >  Accessing a generic struct inside another struct
Accessing a generic struct inside another struct

Time:05-20

I have a some structs,

type Fruit struct {
  Name string
  Sweetness int
}
type Meat struct {
  Name string
  Bloodiness int 
}

Somtimes a person may eat some fruit, sometimes some meat. So we have another struct.

type Person struct {
  Name string
  Mealtype interface{} 
}

It's this 'Mealtype' interface{} bit I kind of made up to fix my issue. Go is allowing me to set either a Mealtype to be a Meat or Fruit struct, however. I can't seem to access any of the internal data from the struct. The fmt.Println( someperson.Mealtype ) doesn't offer my to access either .Bloodiness or .Sweetness

For example, if i do:

f := Fruit{}
f.Name = "Orange"
f.Sweetness = 10

p := Person{}
p.Name = "John"
p.Mealtype = f 

fmt.Println(p.Mealtype.Name) 

I get the error:

p.Mealtype.Name undefined (type interface{} has no field or method Name)

CodePudding user response:

If you want both Fruit and Meat to share a Name value that can be accessed, you probably want to create an interface that the two of them implement. For example

type Food interface {
    Name() string
}

func (f Fruit) Name() string {
    return f.name
}

func (m Meat) Name() string {
    return m.name
}

type Person struct {
  Name string
  Mealtype Food 
}

(note that the Meat and Fruit's field name is now lowercase, to avoid conflict with Name)

Then you should be able to call fmt.Println(p.Mealtype.Name()).

For completeness, you can also use type assertion, but that's probably not what you want to do in your example. But it would look something like this:

if fruit, ok := p.MealType.(Fruit); ok {
    fmt.Println(fruit.Name)
}

CodePudding user response:

You should describe Mealtype interface before using it. Like

type MealtypeInterface interface {
    Name() string
}

Then in type Person you define Mealtype as MealtypeInterface, not interface{}

type Person struct {
  Name string
  Mealtype MealtypeInterface 
}

CodePudding user response:

an interface defines a consistent way of interacting with variables of inconsistent types. So you have to find a way to make accessing Bloodiness of meats and Sweetness of fruits consistently.

Here's one way that Sweetness and Bloodiness could be behind a consistent interface:

interface Edible {
   PrimaryAttribute() (string, int)
}

func (f Fruit)PrimaryAttribute() (string, int) {
  return "Sweetness", f.Sweetness
}

// and return Bloodiness, m.Bloodiness for meats of course

Its common for programmers used to interpreted languages like python or javascript, etc, to think of struct field names as runtime-accessible data. But this is only the case in Go with reflect, which I try to discourage new Go programmers to use. Ask yourself if you wouldn't rather define a consistent method of describing arbitrary named attributes of your edibles:

type Edible struct {
   FlavorAttributes map[string]int
}

fish_meat := &Edible{FlavorAttributes: map[string]int{"Bloodiness": 3}

Now Bloodiness and Sweetness are not fields, they are strings in a map, and more accessible at runtime.

It's common for programming books to talk about polymorphism in terms of human hierarchical organization (class Animal, class Dog, class Labrador hierarchy). Read "Design Patterns: Elements of Reusable Object-Oriented Software" for some good thoughts about why a lot of software doesn't end up implementing class hierarchies from the human perspective.

  •  Tags:  
  • go
  • Related