Timo Huovinen
Timo Huovinen

Reputation: 55623

Does go support multi-types? (type of generics)

type MyNumber interface {
    float32, float64, uint, int // this is not supported
}

func PrintNumber(n MyNumber) {
    switch n.(type) {
    case float32, float64, uint, int:
        fmt.Printf("%v\n", n)
    default:
        panic("PrintNumber only supports types float32, float64, uint, int")
    }
}

In go, you can define a blank interface, which basically allows any type

var v interface{}
v = "string"
v = 0.1

Is there a way to reduce the allowed types to a specific list of types?

Something like

type MyNumber float32, float64, uint, int

or

type MyNumber interface {
    float32, float64, uint, int
}

This is so that I can have the compiler check if the type is going to be supported by the function.

Upvotes: 0

Views: 1656

Answers (3)

blackgreen
blackgreen

Reputation: 44697

Is there a way to reduce the allowed types to a specific list of types?

Yes, starting with Go 1.18, using interfaces with type elements. But such interfaces can only be used as constraints for type parameters. In the draft version of the specs this is mentioned under Interface types:

Interfaces that contain non-interface types, terms of the form ~T, or unions may only be used as type constraints, or as elements of other interfaces used as constraints. They cannot be the types of values or variables, or components of other, non-interface types.

In short, within an interface, beside method signatures and other embedded interfaces, you can now add type elements. This defines a type constraint, i.e. "the set of permissible type arguments for the respective type parameter".

In particular:

  • an interface constraint may specify one or more type elements. It will restrict type parameters to the specified type(s). You can specify more than one type element as a union (more info: Go Generics - Unions)
type MyNumber interface {
    float32 | float64 | uint | int
}
type MyNumber interface {
    ~float32 | ~float64 | ~uint | ~int
}
  • you can still specify method signatures in such an interface. It will restrict type parameters to the set of those types that also implement the interface:
// set of types with underlying int that also implement `String() string`
type MyNumber interface {
    ~int
    String() string
}
  • as a corollary of the above, if the interface specifies non-approximated predeclared types and methods, no type will satisfy the interface (because you can't declare methods on predeclared types)
// empty type set
type MyNumber interface {
    int
    String() string
}

As mentioned earlier, you use this only as a type parameter constraint, you won't be able to use it in type switches, declare variables, etc.:

func foo[T MyNumber](v T) {
    // ...
}

Upvotes: 1

icza
icza

Reputation: 417612

You can't force to a set of concrete types at compile time in a way you'd like to.

What you can do is use interfaces. List the methods in the interface you expect from the implementations. Then it won't matter what the concrete, runtime type is. You interact with the value only via the interface, which is guaranteed at compile time that it is implemented by the passed value.

E.g. if you expect the value to provide an int32 value, use this interface:

type HasInt32 interface {
    Int32() int32
}

func f(i HasInt32) {
    fmt.Println("int32 value:", i.Int32())
}

It won't matter if the passed value has int32 or float64 as its underlying type, or it's whatever else complex type. You need an int32, and that's what you get. The implementors are responsible how to produce that value.

For example:

type MyInt32 int32

func (m MyInt32) Int32() int32 { return int32(m) }

type MyStruct struct {
    i int64
}

func (m MyStruct) Int32() int32 { return int32(m.i) }

Testing it:

var m MyInt32 = 1
f(m)

var s MyStruct = MyStruct{i: 2}
f(s)

Which outputs (try it on the Go Playground):

int32 value: 1
int32 value: 2

Upvotes: 1

Jonathan Hall
Jonathan Hall

Reputation: 79586

Is there a way to reduce the allowed types to a specific list of types [of an empty interface]?

At compile time, no.

You may choose to use a limitation at runtime, however. This should be avoided when there are other alternatives, as it doesn't provide as much safety.

This is done in the standard library in a number of places, such as the json marshaler, which requires a pointer as the target.

Upvotes: 2

Related Questions