0

Say we have 2 structs sharing a property with the same name and purpose, but of different size:

type (
    L16 struct {
        Length uint16
    }

    L32 struct {
        Length uint32
    }
)

The goal is to make those structs have a GetLength method with exactly the same signature and implementation:

func (h *L16) GetLength() int {
    return int(h.Length)
}

func (h *L32) GetLength() int {
    return int(h.Length)
}

— but to avoid repeating the implementation for each struct.

So I try:

type (

    LengthHolder interface {
        GetLength() int
    }

    LengthHolderStruct struct {
        LengthHolder
    }

    L16 struct {
        LengthHolderStruct
        Length uint16
    }

    L32 struct {
        LengthHolderStruct
        Length uint32
    }

)

func (h *LengthHolderStruct) GetLength() int {
    return int(h.Length)
}

— but that errors with h.Length undefined (type *LengthHolderStruct has no field or method Length).

How do we do it?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
Greendrake
  • 3,657
  • 2
  • 18
  • 24
  • 1
    @MrFuppes in my real use-case there are many more structs to have the `GetLength` method. Repeating it per each struct will require big maintenance overhead. – Greendrake Jul 23 '21 at 12:43
  • 1
    @mh-cbon Why would there need to be structs with just one `Length` property? I really assumed it would be _obvious_ that in real use-cases those structs would have more fields. – Greendrake Jul 23 '21 at 12:54

1 Answers1

2

Go 1.17 and below

The unceremonious answer is that you can't you shouldn't. Just implement the method on each struct and make the future you and other maintainers happy.

Anyway, let's say that you absolutely must do it, of course the embedded type knows nothing about the embedding type so you can't reference Length from LengthHolderStruct.

Personally, I think @mh-cbon answer is a decent compromise. To provide an alternative, you could hack around this in a very ugly way by declaring the Length field as an interface{} on the embedded struct and using a type switch (throws type safety in the bin).

I would not use the following code in my production system, but here you go:

func main() {
    l16 := L16{
        LengthHolderStruct: LengthHolderStruct{
            Length: uint16(200), 
            // but nothing stops you from setting uint32(200)
        },
    }
    fmt.Println(l16.GetLength())
}

type (
    LengthHolder interface {
        GetLength() int
    }

    LengthHolderStruct struct {
        Length interface{}
    }

    L16 struct {
        LengthHolderStruct
    }

    L32 struct {
        LengthHolderStruct
    }
)

func (h *LengthHolderStruct) GetLength() int {
    switch t := h.Length.(type) {
    case uint16:
        return int(t)
    case uint32:
        return int(t)
    }
    return 0
}

Go 1.18 and above

Use generics. As long as the types in Constraint can all be converted to int, you can use the following code:

type Constraint interface {
     ~uint16 | ~uint32
}

type LX[T Constraint] struct {
    Length T
}

func (h *LX[T]) GetLength() int {
    return int(h.Length)
}

func main() {
    lx := LX[uint16]{
        Length: uint16(200),
    }
    fmt.Println(lx.GetLength()) // 200
}

Go Playground: https://go.dev/play/p/LYbDrjQkgCN

blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • So, the bottom line is that I _should_ just repeat the method implementation for each struct, even if I have a dozen of them. Really? – Greendrake Jul 23 '21 at 12:56
  • @Greendrake Just like you, I've been searching for a solution to your original question for quite a while, and I've come to the same conclusion .... it's insane that you have repeat your method implementations for multiple struct types that share common functionality. For as "mature" as Golang claims to be, it lacks basic features like this, which are essential for devs. – Trevor Sullivan Apr 26 '23 at 03:43
  • @TrevorSullivan I haven't been doing much Go dev but I hear that *generics* (from v1.18) may be making big difference to the issue in question. – Greendrake Apr 26 '23 at 04:13
  • @Greendrake unfortunately I've been learning about Generics and this issue is still a problem. Thank you though. – Trevor Sullivan Apr 26 '23 at 04:31