Type cast custom types to base types

22.6k views Asked by At

How can I convert custom type to interface{} and then to base type (ex. uint8)?

I can't use direct cast like uint16(val.(Year)) because I may not know all custom types, but I can determinate base types (uint8, uint32,...) in runtime


There are many custom types (usually used as enums) based on numeric:

ex:

type Year  uint16
type Day   uint8
type Month uint8

and so on...

The question is about type casting from interface{} to base types:

package main

import "fmt"

type Year uint16

// ....
//Many others custom types based on uint8

func AsUint16(val interface{}) uint16 {
    return val.(uint16) //FAIL:  cannot convert val (type interface {}) to type uint16: need type assertion
}

func AsUint16_2(val interface{}) uint16 {
    return uint16(val) //FAIL:   cannot convert val (type interface {}) to type uint16: need type assertion
}

func main() {
    fmt.Println(AsUint16_2(Year(2015)))
}

http://play.golang.org/p/cyAnzQ90At

3

There are 3 answers

0
AudioBubble On BEST ANSWER

You can accomplish this by using the reflect package:

package main

import "fmt"
import "reflect"

type Year uint16

func AsUint16(val interface{}) uint16 {
    ref := reflect.ValueOf(val)
    if ref.Kind() != reflect.Uint16 {
        return 0
    }
    return uint16(ref.Uint())
}

func main() {
    fmt.Println(AsUint16(Year(2015)))
}

Depending on your situation, you may want to return (uint16, error), instead of returning the empty value.

https://play.golang.org/p/sYm1jTCMIf

3
Rick-777 On

Why did you include Year in the question? Are you hoping to convert arbitrary things into Years, or convert Years into uint16s?

If I assume you meant the latter case, then it would be better to use a method

func (y Year) AsUint16() uint16 { 
    return uint16(y)
}

which doesn't need any type assertions or reflection.

https://play.golang.org/p/9wCQJe46PU

0
blackgreen On

Go 1.18 and above

After the introduction of generics, it's now easier to write such a function that works for any defined type.

func AsUint16[T ~uint16](v T) uint16 {
     return uint16(v)
}

func AsUint8[T ~uint8](v T) uint8 {
     return uint8(v)
}

Unlike with interface{}, the conversion works because the type parameter is explicitly constrained to types with that specific underlying type using tilde ~ (more info: What's the meaning of the new tilde token ~ in Go?).

The downside is that you must write a different function for each underlying type, as shown above with uint16, uint8, etc. A union constraint would work with a type switch in the function body, but in my opinion it's much clearer to use specialized functions — the result is the same.