I recently came across a code that is doing something I don't understand.
There are multiple structs having the same embedded struct and an interface that defines methods returning pointer to each struct. This interface is implemented by the embedded struct but only 'partially' by the individual structs, as such, each struct only implements the method where the pointer to that struct is returned.
For better understanding, here is the representative code:
type BarStocks interface {
GetVodka() *Vodka
GetMartini() *Martini
GetBourbon() *Bourbon
GetNegroni() *Negroni
GetManhattan() *Manhattan
}
type BaseAttributes struct {
ID uuid.UUID
Quantity float64
CreatedAt time.Time
UpdatedAt time.Time
}
func (e *BaseAttributes) GetVodka() *Vodka {
return nil
}
func (e *BaseAttributes) GetMartini() *Martini {
return nil
}
func (e *BaseAttributes) GetBourbon() *Bourbon {
return nil
}
func (e *BaseAttributes) GetNegroni() *Negroni {
return nil
}
func (e *BaseAttributes) GetManhattan() *Manhattan {
return nil
}
And then each individual struct implements only the method where its pointer is returned, for example:
type Vodka struct {
BaseAttributes
Label string
}
func (v *Vodka) GetVodka() *Vodka {
return v
}
Now in the code, this setup is used to typecast the individual struct to the interface as a pointer, something like this:
func someFunc() BarStocks {
v := Vodka{}
return &v
}
Now I am not too deep into Go yet and so unable to comprehend how the pointer to the struct becomes the same type as the interface.
Thanks in advance for any insight into this.
I'll do my best to answer the question I think you're asking.
The documentation on embedding explains the behavior you're seeing,
There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.
This explains how a Vodka
struct, which embeds struct BaseAttributes
which implements all of the methods in BarStocks
is able to satisfy the interface Barstocks
. This excerpt, however, does not explain how we effectively override GetVodka()
for our Vodka
struct.
To understand this we need to read another excerpt from the documentation.
Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type.
This excerpt explains that If Vodka
implements GetVodka()
and embeds a struct (BaseAttributes)
which also implements GetVodka()
, the outer-most definition is the one that takes precedence.
The combination of these behaviors explain how Vodka
satisfies the BarStocks
interface and has the behavior you see in the example code.
The additional methods missing from Vodka, but present on BarStocks are implemented for BaseAttributes and if the method is missing from Vodka, the method from the embedded strut will be called instead.
Note, methods in an embedded struct will not have access to the parent struct members. Also note, if you wanted to call the embedded struct's method when it has been defined in the parent (as GetVodka is for Vodka) then you would prepend the embedded type's name to the method. For example
myDrink := someFunc()
myDrink.BaseAttributes.GetVodka()
This is obviously not helpful in this case as all of the BaseAttribute methods return nil, but may be helpful in the future.