Skip to content

Add generic ArrayOf[T any] type#1314

Open
arp242 wants to merge 2 commits into
masterfrom
arrayof
Open

Add generic ArrayOf[T any] type#1314
arp242 wants to merge 2 commits into
masterfrom
arrayof

Conversation

@arp242
Copy link
Copy Markdown
Collaborator

@arp242 arp242 commented Apr 7, 2026

Add generic ArrayOf[T any] type

This adds a new generic ArrayOf[T any] type:

	var a pq.ArrayOf[int]
	db.QueryRow("...").Scan(&a)

This replaces all the existing Array* types, which are now implemented
with ArrayOf type aliases and marked as deprecated. The existing tests
are unchanged except for some error texts, and I believe ths should be
fully compatible.

Only GenericArray is left as-is, as re-implementing that with ArrayOf in
a way that's fully compatible is kind of tricky. I'd rather just leave
it as-is instead of spending the effort and risk breakage.

The main issue is that we rely on:

	//go:linkname convertAssign database/sql.convertAssign
	func convertAssign(dest, src any) error

Which does not exactly spark joy, however there is already a comment on
convertAssign():

	// convertAssign should be an internal detail,
	// but widely used packages access it using linkname.
	// Notable members of the hall of shame include:
	//   - ariga.io/entcache
	//
	// Do not remove or change the type signature.
	// See go.dev/issue/67401.

So if compatibility is already guaranteed because of other packages,
then I guess it's okay to join this "hall of shame". Also, it looks like
it'll probably just get exported in the future:
golang/go#62146 (comment)

Fixes #1103

arp242 added 2 commits May 8, 2026 06:44
This adds a new generic ArrayOf[T any] type:

	var a pq.ArrayOf[int]
	db.QueryRow("...").Scan(&a)

This replaces all the existing Array* types, which are now implemented
with ArrayOf type aliases and marked as deprecated. The existing tests
are unchanged except for some error texts, and I believe ths should be
fully compatible.

Only GenericArray is left as-is, as re-implementing that with ArrayOf in
a way that's fully compatible is kind of tricky. I'd rather just leave
it as-is instead of spending the effort and risk breakage.

The main issue is that we rely on:

	//go:linkname convertAssign database/sql.convertAssign
	func convertAssign(dest, src any) error

Which does not exactly spark joy, however there is already a comment on
convertAssign():

	// convertAssign should be an internal detail,
	// but widely used packages access it using linkname.
	// Notable members of the hall of shame include:
	//   - ariga.io/entcache
	//
	// Do not remove or change the type signature.
	// See go.dev/issue/67401.

So if compatibility is already guaranteed because of other packages,
then I guess it's okay to join this "hall of shame". Also, it looks like
it'll probably just get exported in the future:
golang/go#62146 (comment)

Fixes #1103
Copy link
Copy Markdown

@Ferada Ferada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried this on a project and everything still works, replacing the deprecated types with ArrayOf is also easy 👍

Edit: Pushed suggestions below to this commit.

Comment thread deprecated.go
Comment on lines +183 to +196
case *[]bool:
return (*BoolArray)(a)
case *[]float64:
return (*Float64Array)(a)
case *[]float32:
return (*Float32Array)(a)
case *[]int64:
return (*Int64Array)(a)
case *[]int32:
return (*Int32Array)(a)
case *[]string:
return (*StringArray)(a)
case *[][]byte:
return (*ByteaArray)(a)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case *[]bool:
return (*BoolArray)(a)
case *[]float64:
return (*Float64Array)(a)
case *[]float32:
return (*Float32Array)(a)
case *[]int64:
return (*Int64Array)(a)
case *[]int32:
return (*Int32Array)(a)
case *[]string:
return (*StringArray)(a)
case *[][]byte:
return (*ByteaArray)(a)
case *[]bool:
return (*ArrayOf[bool])(a)
case *[]float64:
return (*ArrayOf[float64])(a)
case *[]float32:
return (*ArrayOf[float32])(a)
case *[]int64:
return (*ArrayOf[int64])(a)
case *[]int32:
return (*ArrayOf[int32])(a)
case *[]string:
return (*ArrayOf[string])(a)
case *[][]byte:
return (*ArrayOf[[]byte])(a)

Unless there was a reason to use the deprecated names here?

Comment thread array.go
case float64:
b = strconv.AppendFloat(b, v, 'f', -1, 64)
case bool:
if any(aa).(bool) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if any(aa).(bool) {
if v {

Comment thread array.go
b = strconv.AppendUint(b, rv.Uint(), 10)
case reflect.Float32, reflect.Float64:
b = strconv.AppendFloat(b, rv.Float(), 'f', -1, 32)
case reflect.Ptr:
Copy link
Copy Markdown

@Ferada Ferada May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case reflect.Ptr:
case reflect.Bool:
if rv.Bool() {
b = append(b, 't')
} else {
b = append(b, 'f')
}
case reflect.Ptr:

Probably just missing the boolean case?

Comment thread array.go
b = append(b, del...)
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
return b, del, err
for i, aa := range a {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the element type is already known, wouldn't it be faster to move the type assertion out of the loop for the common cases?

That is, specialise this block for the common slice types via something like:

switch xx := any([]T(a)).(type) {
case []bool:
	...
case []uint8:
	b = appendAllUint(xx, b, del)
case []uint16:
	b = appendAllUint(xx, b, del)
...
default:
	// only use reflection on individual elements here
	for i, aa := range a {
		...
	}
}

The duplication for the signed and unsigned types could also be minimised with something like:

func appendAllUint[T uint8 | uint16 | uint32 | uint64](xx []T, b []byte, del []byte) []byte {
	for i, aa := range xx {
		if i > 0 {
			b = append(b, del...)
		}
		b = strconv.AppendUint(b, uint64(aa), 10)
	}
	return b
}

// similar for the other types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rewrite the array.go to support generic array type?

2 participants