Making widening integer conversions safely

69 views Asked by At

I need to make a few safe widening integer conversions – uint32 to uint and uint to uint64, for example. I know it’s possible to achieve this with an explicit type conversion:

x := uint32(1) // suppose

y := uint(x)

But if x’s type changes later on, this will keep compiling while now doing the wrong thing.

x := int32(-1)

y := uint(x) // y is now 4294967295 or 18446744073709551615
x := uint64(4294967296)

y := uint(x) // y is now zero if uint is 32 bits

Is there an idiomatic way to perform an integer conversion that must either preserve the original value or fail to compile? The best I can come up with is to have a package of functions like this, which doesn’t seem worth it:

func Uint32ToUint(x uint32) uint {
    return uint(x)
}
2

There are 2 answers

3
ruakh On BEST ANSWER

I don't know of a very clean approach, but if there potentially a bunch of different types that you want to allow to be cast to uint, then instead of defining a separate function for each such cast, another option is to define an interface with the appropriate set of types, and then define a generic function that's defined only for types in that set:

type SafeToConvertToUInt interface {
    uint | uint32 | uint64
}

func ToUInt[T SafeToConvertToUInt](x T) uint {
    return uint(x)
}

[playground link]

0
blackgreen On

You can use the generics approach to have compile-time guarantees about what types are accepted — with a small modification to make it more broadly useful — but that won't help when targeting different architectures. For that, you can use build constraints so that the appropriate version of your function is compiled into the executable.

As an example, build for 32-bit linux:

//go:build linux && 386

func ConvertUint[T ~uint | ~uint32](x T) uint {
    return uint(x)
}

or build for 64-bit MacOS with Apple chip:

//go:build darwin && arm64

func ConvertUint[T ~uint | ~uint32 | ~uint64](x T) uint {
    return uint(x)
}

I used random build tags as an example. Your actual build conditions could be more complex than that. You can see which are Go's supported distributions, and have an idea of what build tags you might use with:

go tool dist list

When building with go build, the toolchain will pick up the GOOS and GOARCH environment variables and build the appropriate file. Otherwise you can pass the tags explicitly to the build command as shown in this question.