Sum by pointers which are constrained by type parameters

Viewed 68

This code works fine:

type Numeric interface {
    int32 | int64
}

func SumByPointer[T1 Numeric, T2 Numeric](v1 *T1, v2 *T2) {
    *v1 = *v1 + T1(*v2)
}

I want the pointer types to be constrained by type parameters. This is not compiled:

// Err: ./prog.go:11:23: interface contains type constraints    
func SumByPointer[T1 *Numeric, T2 *Numeric](v1 T1, v2 T2) {

Here definition is compiled but call to function is not:

func SumByPointer[T0 Numeric, T1 *T0, T2 *T0](v1 T1, v2 T2) {
....
// Err: ./prog.go:19:15: *int64 does not implement *int32
SumByPointer(&v1, &v2)

This works:

func SumByPointer[T0 Numeric, T02 Numeric, T1 *T0, T2 *T02](v1 T1, v2 T2) {

Questions:

  • Is this expected that *Numeric is not allowed but T0 Numeric, T1 *T0 works?
  • Is there easier way than latter one?
1 Answers

As a foreword, note that converting between integers may result in truncation. In particular, your code is open to subtle bugs if you rely on type inference at call sites, because then truncation may occur depending on the order of the function arguments.

To be clear:

v1 := int32(10)
v2 := int64(20)
SumByPointer(&v1, &v2)
SumByPointer(&v2, &v1)

Either call compiles, but the first one converts int64 to int32, which may truncate. With that said:

Is this expected that *Numeric is not allowed

Yes it is. Your constraint Numeric includes a type term, specifically the union int32 | int64, which makes it a non-basic interface, and non-basic interfaces can only be used as type constraints.

When you write T1 *Numeric, Numeric is not used as a constraint anymore. It is used as part of a type literal (a pointer). This becomes easier to see if you consider that T1 *Numeric is actually syntactic sugar for:

T1 interface{ *Numeric }

When you write T0 Numeric, T1 *T0 instead:

  • Numeric is again used a constraint of T0
  • T0 is not an interface, it is a type parameter
  • you can use T0 as part of the type literal *T0, which then can be used as the constraint for T1 — equivalent to T1 interface { *T0 }

Is there easier way than latter one?

Your first attempt is quite easy, I would stick to it:

func SumByPointer[T1 Numeric, T2 Numeric](v1 *T1, v2 *T2) {
    *v1 = *v1 + T1(*v2)
}

Hiding the pointer behind a type parameter doesn't really accomplish much. In fact, for the addition to compile, you have to keep the pointers out of the union. And for the conversion to be valid you have to use the base types. So your final attempt is also sort of forced, as far as I can see.

An alternative might be to parametrize Numeric:

type Numeric[T int32 | int64] interface {
    *T
}

func SumByPointer[T1 Numeric[N], T2 Numeric[M], N, M int32 | int64](v1 T1, v2 T2) {
    *v1 = *v1 + N(*v2)
}

Although whether this appears "easier" is entirely up to you. In practical terms, the Numeric interface is still non-basic.

Related