Generic and typecasting

Hi there!
I want to write a simple method to convert GO basic type (int(8/16/32) / uint(8/16/32)) to cgo type (C.int / C.uint e.t.c.)
I want to use generics but met a problem with type-casting.
see the following code:

// wrapCType is a generic method to convert GOTYPE (int32/float32 e.t.c.) into CTYPE (c_int/c_float e.t.c.)
func wrapCType[CTYPE any, GOTYPE any](goValue *GOTYPE) (wrapped *CTYPE, finisher func()) {
	if goValue != nil {
		cValue := CTYPE(any(*goValue))
		wrapped = &cValue
		finisher = func() {
			*goValue = GOTYPE(any(cValue))
		}
	} else {
		finisher = func() {}
	}

	return
}

This doesn’t work because CTYPE and GOTYPE could be anything, so you can’t just use type-conversion syntax. You might be able to do this with constraints on CTYPE and GOTYPE like this:

type CNumber interface {
  ~C.char | ~C.short | ~C.int | ~C.long |
  ~C.uchar | ~C.ushort | ~C.uint | ~C.ulong |
  C.float | C.double
}

type GoNumber interface {
  ~int8 | ~int16 | ~int32 | ~int64 |
  ~uint8 | ~uint16 | ~uint32 | ~uint64 |
  ~float32 | ~float64
}

func wrapCType[CTYPE CNumber, GOTYPE GoNumber](goValue *GOTYPE) (wrapped *CTYPE, finisher func())

But I’m not sure; I haven’t been at a computer for a but and the Go playground doesn’t work when you import "C"

thank you for your answer!

sadly, it produces the following errors:

../type_wrapper.go:33:2: invalid use of ~ (underlying type of _Ctype_char is int8)
../type_wrapper.go:33:12: invalid use of ~ (underlying type of _Ctype_short is int16)
../type_wrapper.go:33:23: invalid use of ~ (underlying type of _Ctype_int is int32)
../type_wrapper.go:33:32: invalid use of ~ (underlying type of _Ctype_long is int64)
../type_wrapper.go:34:3: invalid use of ~ (underlying type of _Ctype_uchar is uint8)
../type_wrapper.go:34:14: invalid use of ~ (underlying type of _Ctype_ushort is uint16)
../type_wrapper.go:34:26: invalid use of ~ (underlying type of _Ctype_uint is uint32)
../type_wrapper.go:34:36: invalid use of ~ (underlying type of _Ctype_ulong is uint64)

also, may I ask what is the ~?

oh, ok, it seems that I’ve fixed that :smile:

ype CNumber interface {
	C.char | C.short | C.int | C.long |
	C.uchar | C.ushort | C.uint | C.ulong |
	C.float | C.double
}

type GoNumber interface {
	~int8 | ~int16 | ~int32 | ~int64 |
	~uint8 | ~uint16 | ~uint32 | ~uint64 |
	~float32 | ~float64
}

// wrapCType is a generic method to convert GOTYPE (int32/float32 e.t.c.) into CTYPE (c_int/c_float e.t.c.)
func wrapCType[CTYPE CNumber, GOTYPE GoNumber](goValue *GOTYPE) (wrapped *CTYPE, finisher func()) {
	if goValue != nil {
		cValue := CTYPE(*goValue)
		wrapped = &cValue
		finisher = func() {
			*goValue = GOTYPE(cValue)
		}
	} else {
		finisher = func() {}
	}

	return
}

however, goland complains about that conversion

Yes:

Tilde ("~") in type constraints

In Go, generic types can have constraints (this is why we swapped wrapCType[CTYPE any, ...] to wrapCType[CTYPE CNumber, ...]) which are defined as interfaces. When you write something like:

type Integer interface {
    int | int8 | int16 | int32 | int64
}

func add[T Integer](a, b T) T { return a + b }

The Integer constraint says that the type, T, must be an int, int8, etc.

However, if you do this:

type MyInt int

var a MyInt = 1
var b MyInt = 2

c := add(a, b)

It won’t work because a and b are of type MyInt, not int, which isn’t in the constraint list.

The tilde (“~”) in a constraint like ~int means that the type doesn’t need to be int exactly; it can be any type whose underlying type is int (such as MyInt in my example).


For some background...

The “C” package you get when you write import "C" isn’t a “real” package like, for example, the “fmt” package is. It’s a “virtual” package that tells the Go compiler to do fancy stuff to “translate” between Go and C conventions (e.g. the compiler does something different when calling a Go function vs. calling a C function, etc.). I’m not super familiar with cgo, so I wasn’t sure what kind of “magic” the compiler uses for the C.int, C.char, etc. types. Based on that error message, it looks like C.char gets translated to an internal _Ctype_char type and that type’s underlying type is int8 (so it’s like somewhere in Cgo it says type char = _Ctype_char and type _Ctype_char = int8).

Anyway, based on this error:

underlying type of _Ctype_char is int8

and the other ones like, it looks like you don’t need the CNumber interface and it really all boils down to GoNumber, so you could just say:

func wrapCType[CTYPE, GOTYPE GoNumber](goValue *GOTYPE) (wrapped *CTYPE, finisher func())

At this point, though, I’m not sure what to suggest because I’m not sure if all of the conversions between all of these types are allowed. Can you show an example of how you would use this wrapCType function? Maybe I or someone else will have better suggestions after seeing what you’re trying to do.