Search code examples
gomethodsstructinterface

Golang: implicit struct matching


Consider this code:

type Rectangle struct {
    Width, Height, Area int
}

type Square struct {
    Side, Area int
}

type Geometry struct {
    Area int
}

func SumGeometries(geometries ...Geometry) (sum int) {
    for _, g := range geometries {
        sum += g.Area
    }
    return
}

func TestSumGeometries(t *testing.T) {
    rect := Rectangle{5, 4, 20}
    square := Square{5, 25}

    got := SumGeometries(rect, square)      // cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
    want := 45

    if got != want {
        t.Error("fail!")
    }
}

I want MyFunc to take whatever struct that contains Apple, not just BStruct in specific.
Is this achievable in Go?

The only way I can find ATM is the following:

type Rectangle struct {
    Width, Height, Area int
}

func (r *Rectangle) GetArea() int {
    return r.Area
}

type Square struct {
    Side, Area int
}

func (s *Square) GetArea() int {
    return s.Area
}

type Areaer interface {
    GetArea() int
}

func SumGeometries(geometries ...Areaer) (sum int) {
    for _, s := range geometries {
        sum += s.GetArea()
    }
    return
}

func TestArgs(t *testing.T) {
    rect := Rectangle{5, 4, 20}
    square := Square{5, 25}

    got := SumGeometries(&rect, &square)        // cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
    want := 45

    if got != want {
        t.Error("fail!")
    }
}

It feels perhaps not idiomatic: would I want to pollute my struct with an unnecessary method when I'd be already happy with consumers accessing the data directly?


Solution

  • Adding methods to types it not "polluting".

    But there's a way to achieve what you want without repetition. Define an Area type holding the common (here Area) field with GetArea() method:

    type Area struct {
        Value int
    }
    
    func (a Area) GetArea() int {
        return a.Value
    }
    

    And embed this in other types:

    type Rectangle struct {
        Width, Height int
        Area
    }
    
    type Square struct {
        Side int
        Area
    }
    

    This way the GetArea() method gets promoted, and Rectangle and Square will automatically implement Areaer. Testing it:

    rect := Rectangle{5, 4, Area{20}}
    square := Square{5, Area{25}}
    
    got := SumGeometries(rect, square)
    
    want := 45
    
    if got != want {
        fmt.Println("fail!")
    }
    

    Outputs nothing (no error). Try it on the Go Playground.

    Note that if Area contains a single field only, you can even omit the wrapper struct and use int as the underlying type directly:

    type Area int
    
    func (a Area) GetArea() int {
        return int(a)
    }
    

    Then using it is simpler:

    rect := Rectangle{5, 4, 20}
    square := Square{5, 25}
    

    Try this one on the Go Playground.