Home > Software engineering >  Switch on interface type without type assertion
Switch on interface type without type assertion

Time:10-20

I have a function that can take a number of different argument types. I'd like to use a type switch and reduce code duplication as much as possible. As a very basic example, here I want to copy both uint8 and int8 types into a byte buffer. This code happily works

package main

func switchFn(args ...interface{}) {
    var buf []byte
    for _, arg := range args {
        switch val := arg.(type) {
        case uint8:
            buf = append(buf, byte(val))
        case int8:
            buf = append(buf, byte(val))
        }
    }
}

func main() {
    switchFn(int8(42), uint8(42)) // etc
}

You'll notice both the case statements do exactly the same thing! If I combine them though...

package main

func switchFn(args ...interface{}) {
    var buf []byte
    for _, arg := range args {
        switch val := arg.(type) {
        case uint8, int8:
            buf = append(buf, byte(val))
        }
    }
}

func main() {
    switchFn(int8(42), uint8(42)) // etc
}

I run into an issue of cannot convert val (type interface {}) to type byte: need type assertion. But I'm literally switching on the type! Argh!

Am I stuck with the code duplication here, or is there a smarter way to do this? Note that the copying into byte buffer is used to illustrate the example, my function may be doing other things in the case blocks.

CodePudding user response:

Here's a way to avoid the code duplication, at the cost of ... well, a different sort of code duplication:

func switchFn(args ...interface{}) {
    var buf []byte
    for _, arg := range args {
        var val byte
        switch v := arg.(type) {
        case uint8:
            val = byte(v)
        case int8:
            val = byte(v)
        default:
            panic("wrong type")
        }
        buf = append(buf, val)
    }
}

For this particular function, the original duplication is probably better. Should the buf = append(buf, val) section get larger or more complicated, this would probably be better.

In still other cases—perhaps most real ones—the method gopher suggests is probably best:

    f := func(val byte) {
        buffer = append(buffer, val)
    }

You can now call f from each case.

CodePudding user response:

The cases can be combined, but val will have type interface{} in the block. That's not useful for your scenario.

Use a function to reduce code duplication.

func switchFn(args ...interface{}) {
    var buf []byte

    byteFn := func(b byte) {
        buf = append(buf, b)
    }

    for _, arg := range args {
        switch val := arg.(type) {
        case uint8:
            byteFn(val)
        case int8:
            byteFn(byte(val))
        }
    }
}

The reflect API will not help because separate code is required for the signed and unsigned values. The reflect API is helpful for combining all signed integers into block of code and all unsigned integers into another block of code.

for _, arg := range args {
    switch val := arg.(type) {
    case int, int8, int16, int32, int64:
        i := reflect.ValueOf(val).Int()
        // i is an int64
        fmt.Println(i)
    case uint, uint8, uint16, uint32, uint64:
        u := reflect.ValueOf(val).Uint()
        // u is an uint64
        fmt.Println(u)
    }
}
  • Related