Search code examples
reflectiongostructinterfaceembedding

Accessing embedded type fields through interface


It seems like I missed something important but I can not figure out what it is. I use reflect to access embedded type fields through an interface. The problem I have is that according to runtime/pprof it eats up a lot of CPU. I do not like to implement Setter and Getter methods on all Vehicles so is there a better way of doing this?

Simplified sample:

package main

import(
    "reflect"
    "fmt"
)

// the "contract" is that all vehicles have an embedded Engine
type Vehicle interface {}

type Engine struct {
    Power float64
    Cubic float64
}

type Car struct {
    Engine
    Weight float64
    TopSpeed float64
}

// more Vehicles with Engines here...

func EngineCheck(v Vehicle) {
    // this does not work:
    //power := v.Power
    // reflection works but eats up a lot of CPU:
    power := reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
    fmt.Println(power)
}

func main() {
    c1 := &Car{Engine{120.0, 1.2}, 1.5, 250}

    EngineCheck(c1)
}

Solution

  • You could use type assertion if you know the exact type which is fast, and only revert to reflection if that fails.

    For example:

    func EngineCheck(v Vehicle) {
        var power float64
        if eng, ok := v.(*Car); ok {
            power = eng.Power
        } else {
            power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
        }
        fmt.Println(power)
    }
    

    Note that the types Car and *Car are different, and the above example would only "skip" the reflection part if the value you pass is indeed a pointer: *Car.

    If there are multiple possible "acceptable" types, you could use a type switch. For example if you pass a Car or *Car, you can get the Power value from both. Also if Engine or *Engine would be passed, the same thing applies.

    func EngineCheck(v Vehicle) {
        var power float64
        switch i := v.(type) {
        case *Car:
            power = i.Power
        case Car:
            power = i.Power
        case *Engine:
            power = i.Power
        case Engine:
            power = i.Power
        default:
            power = reflect.ValueOf(v).Elem().FieldByName("Power").Interface().(float64)
        }
        fmt.Println(power)
    }
    

    But the idiomatic solution would still be to add a getter function to Vehicle:

    type Vehicle interface {
        GetPower() float64
    }
    

    Note that you do not have to implement GetPower() everywhere. If you implement it at the Engine:

    func (e Engine) GetPower() float64 {
        return e.Power
    }
    

    And you embed Engine into Car (as you did), your Car type will automatically have this GetPower() method (promoted) in its method set and thus it will automatically implement Vehicle. And then your EngineCheck() function would be as simple as:

    func EngineCheck(v Vehicle) {
        fmt.Println(v.GetPower())
    }
    

    Try all these 3 variants on the Go Playground.