Alex La Bianca
Alex La Bianca

Reputation: 43

Golang proper use of interfaces

I am new to Go and am running into a situation that I am unsure how to solve. I am working on some code that takes a DNS packet in raw bytes and returns a struct called DNSPacket.

The struct looks like the following

type DNSPacket struct {
    ...some fields
    Questions  []Question
    Answers    []Answer
    ...some more fields
}

The issue I am having is with the Answers type which looks like this.

 type Answer struct {
    Name     string
    Type     int
    Class    int
    TTL      uint32
    RdLength int
    Data     []byte
}

Depending on the type of Answer the Data field must be decoded differently. For example if the Answer is an A record (Type 1) the data is simply an ipv4 address. However if the Answer is an SRV record (Type 33) then the data is contains port, priority, weight and target encoded in the byte slice.

I thought it would be great if I could have a method on Answer called DecodeData() that returns the correct data depending on the type, however since there is no overriding or inheritance in Go I am unsure how to solve this. I tried using an interface to solve this, but it would not compile. I tried something like

type DNSRecordType interface {
    Decode(data []byte)
}


type RecordTypeSRV struct {
   target string
   ...more fields
}
//to 'implement' the DNSRecordType interface
func (record *RecordTypeSRV) Decode(data []byte) {
    //do the work to decode appropriately and set
    //the fields on the record
}

Then in the Answer method

func (a *Answer) DecodeData() DNSRecordType {
    if a.Type === SRVType {
       record := RecordTypeSRV{}
       record.Decode(a.Data)
       return record
    }

    //do something similar for other record types
 }

What would be the correct Go way of having a single Answer type, but be able to return different types of Answer Data depending on their type? Sorry, if this is a completely beginner question as I am still very new to Go.

Thanks!

Upvotes: 2

Views: 926

Answers (2)

Bob Fred
Bob Fred

Reputation: 211

As I know, to return different types, the return param must be an interface. So you can simply declare the function like this:

func (a *Answer) DecodeData() (mode modeType, value interface{}) {}

mode means the value is A record or SRV record, and you can return anything you want with the value field.

The function caller can handle the value depending on mode

If you want the code be more elegant, you can define different value structs for each mode. Then the caller may act as below:

type modeType int

const (
    ARecord modeType = 1
    SRVRecord modeType = 2
)

switch mode {
    case ARecord:
    // do something
    case SRVRecord:
    // do something
} 

Upvotes: 1

Melvin Davis
Melvin Davis

Reputation: 271

Let me summarize your question.

You have a DNS Packet with the list of Answers. Based on the type of answer you have to process the data in the answer.

type DNSPacket struct {
    ...some fields
    Questions  []Question
    Answers    []Answer
    ...some more fields
}
type Answer struct {
    Name     string
    Type     int
    Class    int
    TTL      uint32
    RdLength int
    Data     []byte
}

Answer Let's create an interface that should be implemented to process data.

type PacketProcessor interface {
    Process(Answer)
}

Let SRV implements the PacketProcessor

type SRV struct {
    ...
}

func (s *SRV) Process(a Answer) {
    ...
}

Your processing logic should be as follows

func (a *Answer) Process() {
    var p PacketProcessor
    switch a.Type {
        case SRVType:
        p = &SRV{}
        ...
        //other cases
    }

    //finally
    p.Process(*a)
}

Hope it helps :). There is a Gurgaon based golang community that is always ready to help developers with their problems. You can join the community via slack

Upvotes: 5

Related Questions