Home > Software engineering >  Why can't I assign a embedded struct to a parent struct in go?
Why can't I assign a embedded struct to a parent struct in go?

Time:01-23

I have below code try to assign embed struct to its parent struct. There are two set of structure: Guider is the parent struct, DataBlock extends from it. The method func Put(x Guider) accept a parameter with type Guider. It works when I pass a DataBlock variable.

However, the other case is Mock extends from zerolog.Event, but it fails to pass the parameter on the method Test(e zerolog.Event)

I got the following error:

cannot use m (variable of type Mock) as type zerolog.Event in argument to Test

Why are these two cases works differently? How can I make them both work?

package main

import (
    "fmt"

    "github.com/rs/zerolog"
)

type Guider interface {
    Guid() string
}
type FSEntity struct {
    guid string
}

func (e FSEntity) Guid() string {
    return e.guid
}

func Put(x Guider) {
    fmt.Printf("% v\n", x)

}

type Mock struct {
    zerolog.Event
}

func Test(e zerolog.Event) {

}

//Child struct:

type DataBlock struct {
    FSEntity
    data []byte
}

func main() {
    myVar := DataBlock{}
    myVar.guid = "test"
    myVar.data = []byte("moar test")
    Put(myVar) // it works

    m := Mock{}
    Test(m) // it doesn't work. cannot use m (variable of type Mock) as type zerolog.Event in argument to Test
}

CodePudding user response:

First, a couple of definitions:

Polymorphism

Polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.

Subtyping

Subtyping (also subtype polymorphism or inclusion polymorphism) is a form of type polymorphism in which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype

Inheritance

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation.

Object composition

Object composition and object aggregation are closely related ways to combine objects or data types into more complex ones.


Golang follows composition over inheritance principle, e.g. it doesn't support inheritance. So when you're saying

Mock extends from zerolog.Event

you actually mean that Mock includes zerolog.Event struct.

The way Golang implements polymorphism is interface. All types that implement some interface can be used in its place. It's what you see when use Guider.

However, it doesn't work for simple structs. zerolog.Event is a struct inside Mock.

So, normally, Test function should accept some interface as a parameter, and both mock and real event should implement this interface. However, it looks like zerolog doesn't provide interface for Event. So instead you should access the Event field of you struct. Example

CodePudding user response:

Take this with a grain of salt, since I'm not familiar with zerolog package.

Your Guider is an interface, which might have any underlying type as long as Guid() method is satisfied. I assume this is happening through DataBlock containing FSEntity, which itself implements Guid() method, therefore satisfies MIGHT the interface.

On the other hand, I don't know what methods should be implemented to satisfy zerolog.Event or if it's even an interface, or a struct straight up. If it's an interface, you might need to implement it's required methods to be able to use DataBlock as zerolog.Event type. You might want/need to dig into that direction for some very specific answers.

CodePudding user response:

Put(myVar) is legal because myVar is a DataBlock which contains (not inherits from and not implements) an FSEntity which in turn implements the Guider interface.

Since Put accepts a Guider, the reference to myVar is compatible, by virtue of the anonymous FSEntity field it contains which implements Guider. The implementation of Guider on FSEntity is (in effect) elevated to the containing struct (providing a means of delegating interfaces). This only occurs if the contained field is anonymous.

But in the case of Test(m), the function accepts a zerolog.Event which is a struct type, not an interface. As such, there is no "delegation" possible. Test() must be passed a zerolog.Event and in this scenario, this requires that you use the type name of the anonymous field:

Type(m.Event)

Some bonus info:

If DataBlock contained two anonymous fields which both implemented Guider then implicit delegation/elevation cannot take place; golang does not know which of the contained implementations should be delegated to/elevated (if any). In that scenario you must again use the name of the field that you wish to pass to the Put() function:

   // given... 
   type Foo string

   func (f Foo) Guid() string {
      return string(f)
   }

   // and...
   type DataBlock struct {
      FSEntity
      Foo
      data []byte
   }

   // then...
   Put(myVar)   // is now illegal

   // and must instead use either/or:
   Put(myVar.FSEntity)
   Put(myVar.Foo)

Whether implicit or explicit, the crucial distinction is that it is a field of the DataBlock (myVar) that is passed to Put(), not myVar itself.

If you want to pass the DataBlock to Put(), using a Guider interface, then DataBlock must itself implement the Guider interface.

  • Related