Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for unions in Go

Tags:

go

unions

Go has no unions. But unions are necessary in many places. XML makes excessive use of unions or choice types. I tried to find out, which is the preferred way to work around the missing unions. As an example I tried to write Go code for the non terminal Misc in the XML standard which can be either a comment, a processing instruction or white space.

Writing code for the three base types is quite simple. They map to character arrays and a struct.

type Comment Chars

type ProcessingInstruction struct {
    Target *Chars
    Data *Chars
}

type WhiteSpace Chars

But when I finished the code for the union, it got quite bloated with many redundant functions. Obviously there must be a container struct.

type Misc struct {
    value interface {}
}

In order to make sure that the container holds only the three allowed types I made the value private and I had to write for each type a constructor.

func MiscComment(c *Comment) *Misc {
    return &Misc{c}
}

func MiscProcessingInstruction (pi *ProcessingInstruction) *Misc {
    return &Misc{pi}
}

func MiscWhiteSpace (ws *WhiteSpace) *Misc {
    return &Misc{ws}
}

In order to be able to test the contents of the union it was necessary to write three predicates:

func (m Misc) IsComment () bool {
    _, itis := m.value.(*Comment)
    return itis
}

func (m Misc) IsProcessingInstruction () bool {
    _, itis := m.value.(*ProcessingInstruction)
    return itis
}

func (m Misc) IsWhiteSpace () bool {
    _, itis := m.value.(*WhiteSpace)
    return itis
}

And in order to get the correctly typed elements it was necessary to write three getters.

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

func (m Misc) ProcessingInstruction () *ProcessingInstruction {
    return m.value.(*ProcessingInstruction)
}

func (m Misc) WhiteSpace () *WhiteSpace {
    return m.value.(*WhiteSpace)
}

After this I was able to create an array of Misc types and use it:

func main () {

    miscs := []*Misc{
        MiscComment((*Comment)(NewChars("comment"))),
        MiscProcessingInstruction(&ProcessingInstruction{
            NewChars("target"),
            NewChars("data")}),
        MiscWhiteSpace((*WhiteSpace)(NewChars(" \n")))}

    for _, misc := range miscs {
        if (misc.IsComment()) {
            fmt.Println ((*Chars)(misc.Comment()))
        } else if (misc.IsProcessingInstruction()) {
            fmt.Println (*misc.ProcessingInstruction())
        } else if (misc.IsWhiteSpace()) {
            fmt.Println ((*Chars)(misc.WhiteSpace()))
        } else {
            panic ("invalid misc");
        }
    }
}

You see there is much code looking almost the same. And it will be the same for any other union. So my question is: Is there any way to simplify the way to deal with unions in Go?

Go claims to simplify programing work by removing redundancy. But I think the above example shows the exact opposite. Did I miss anything?

Here is the complete example: http://play.golang.org/p/Zv8rYX-aFr

like image 218
ceving Avatar asked Feb 04 '14 13:02

ceving


People also ask

Does go support unions?

Go doesn't have unions, and go structs can't have fields that alias other fields, so C unions can not be represented by an "equivalent" type.

Does golang have unions?

As known, go has no union type, and should only be simulated via interface.

Are there classes in go?

Go does not have classes. However, you can define methods on types. A method is a function with a special receiver argument.

What does New do in Golang?

New() Function in Golang is used to get the Value representing a pointer to a new zero value for the specified type. To access this function, one needs to imports the reflect package in the program.


2 Answers

As it seems that you're asking because you want type safety, I would firstly argue that your initial solution is already unsafe as you have

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

which will panic if you haven't checked IsComment before. Therefore this solution has no benefits over a type switch as proposed by Volker.

Since you want to group your code you could write a function that determines what a Misc element is:

func IsMisc(v {}interface) bool {
    switch v.(type) {
        case Comment: return true
        // ...
    }
}

That, however, would bring you no compiler type checking either.

If you want to be able to identify something as Misc by the compiler then you should consider creating an interface that marks something as Misc:

type Misc interface {
    ImplementsMisc()
}

type Comment Chars
func (c Comment) ImplementsMisc() {}

type ProcessingInstruction
func (p ProcessingInstruction) ImplementsMisc() {}

This way you could write functions that are only handling misc. objects and get decide later what you really want to handle (Comments, instructions, ...) in these functions.

If you want to mimic unions then the way you wrote it is the way to go as far as I know.

like image 102
nemo Avatar answered Sep 25 '22 01:09

nemo


I think this amount of code might be reduced, e.g. I personally do not think that safeguarding type Misc against containing "illegal" stuff is really helpful: A simple type Misc interface{} would do, or?

With that you spare the constructors and all the Is{Comment,ProcessingInstruction,WhiteSpace} methods boil down to a type switch

switch m := misc.(type) {
    Comment: fmt.Println(m)
    ... 
    default: panic()
}

Thats what package encoding/xml does with Token.

like image 22
Volker Avatar answered Sep 26 '22 01:09

Volker