Why causes []any trouble in type equations?

219 views
Skip to first unread message

Torsten Bronger

unread,
Oct 10, 2023, 3:01:44 AM10/10/23
to golan...@googlegroups.com
Hallöchen!

The two most recent Go blog articles make we wonder why

package main

func do[T []E, E any](slice T) {
}

func main() {
var a []float64
do(a)
}

is valid while

package main

func do[T []any](slice T) {
}

func main() {
var a []float64
do(a)
}

is not. The error message doe not help me. Can someone explain
this? Thank you!

Regards,
Torsten.

--
Torsten Bronger

Axel Wagner

unread,
Oct 10, 2023, 3:16:17 AM10/10/23
to golan...@googlegroups.com
In the second case, the type argument is inferred to be `[]float64`. The constraint on `T` in `do` is that it has to be `[]any`. `[]float64` is not `[]any`, the two are different types, so instantiation fails.
Note that even if you explicitly instantiate `do` to `[]any`, you still could not pass a `[]float64` to it. That's because slices are invariant in Go, for good reasons.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/87bkd7nern.fsf%40physik.rwth-aachen.de.

Kurtis Rader

unread,
Oct 10, 2023, 3:18:40 AM10/10/23
to golan...@googlegroups.com
This has nothing to do with generics. It is a FAQ regarding the conversion of a container type value. You cannot modify the type of the object in a container in a called context. This has been true before generics was introduced. Ignoring generics what do you think should happen if you call a function that requires a `[]any` slice and pass it a `[]float64` slice? Consider this pre-generic example:

package main

func do([]any) {

}

func main() {
var a []float64
do(a)
}
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/87bkd7nern.fsf%40physik.rwth-aachen.de.


--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

Torsten Bronger

unread,
Oct 10, 2023, 11:25:29 AM10/10/23
to golan...@googlegroups.com
Hallöchen!

Kurtis Rader writes:

> This has nothing to do with generics. It is a FAQ regarding the
> conversion of a container type value. You cannot modify the type of
> the object in a container in a called context. This has been true
> before generics was introduced. Ignoring generics what do you think
> should happen if you call a function that requires a `[]any` slice
> and pass it a `[]float64` slice? Consider this pre-generic example:
>
> package main
>
> func do([]any) {
> }
>
> func main() {
> var a []float64
> do(a)
> }

Naively, I would have expected that the same happens as with my
first example.

Axel Wagner

unread,
Oct 10, 2023, 3:40:01 PM10/10/23
to golan...@googlegroups.com
In the first example, the inferred type is `float64`, so the signature of the function is `func do([]float64)`. You can obviously pass a `[]float64` to it.
If X is a concrete (i.e. non-interface) type, then `func F[T X]()` is syntactic sugar for `func F[T interface{ X }]()`, which is a constraint that has a single union-element and that union-element lists a single type. So `func do[T []any](v T)` allows to instantiate the function with only a single type: `[]any`. Because `[]any` is not an interface type.
And you can't pass a `[]float64` to a function accepting an `[]any`, because slices are invariant.
The two signatures really mean completely different things. Writing `func do[S []E, E any]` really means "S has type []E, where E can be any type", which is a very different thing from saying "it's a slice of the specific type any".

Torsten Bronger

unread,
Oct 11, 2023, 2:11:44 PM10/11/23
to golan...@googlegroups.com
Hallöchen!

'Axel Wagner' via golang-nuts writes:

> [...]
>
> The two signatures really mean completely different things. Writing
> `func do[S []E, E any]` really means "S has type []E, where E can be
> any type", which is a very different thing from saying "it's a slice
> of the specific type any".

This is true of course but in my head it is easier to consider
instantiation of a generic function as an extra intermediate step,
so that the compiler never sees []any but []float64 directly (as
function parameter type).

Then, all boils down to the fact that you can’t pass []float64 as an
[]any. To be honest, I still don’t fully understand why this is
forbidden, so I just accept that the language does not allow it.

Thanks to both of you!

Jan Mercl

unread,
Oct 11, 2023, 2:25:17 PM10/11/23
to golan...@googlegroups.com
On Wed, Oct 11, 2023 at 8:11 PM Torsten Bronger
<bro...@physik.rwth-aachen.de> wrote:

> Then, all boils down to the fact that you can’t pass []float64 as an
> []any. To be honest, I still don’t fully understand why this is
> forbidden, so I just accept that the language does not allow it.

It's the same reason why one cannot pass, say []byte as an []int. The
backing arrays of the two slices have incompatible memory layouts. To
make it work the compiler would have to inject code like
(schematically)

var tmp []int
for i, v := range byteSlice {
tmp = append(tmp, v)
}
callTheFuntion(tmp)

It is doable but it has a cost which the language designers preferred
not to hide.

Bakul Shah

unread,
Oct 11, 2023, 2:27:01 PM10/11/23
to Torsten Bronger, golan...@googlegroups.com
On Oct 11, 2023, at 11:09 AM, Torsten Bronger <bro...@physik.rwth-aachen.de> wrote:
>
> Then, all boils down to the fact that you can’t pass []float64 as an
> []any. To be honest, I still don’t fully understand why this is
> forbidden, so I just accept that the language does not allow it.

I think in the first case []any is really []interface{}
and []float64 is not compatible with it. While in the second
case []E where E is any type is a *constraint*. That is "any"
has two different meanings in two different contexts.

Axel Wagner

unread,
Oct 11, 2023, 2:46:30 PM10/11/23
to golan...@googlegroups.com
On Wed, Oct 11, 2023 at 8:11 PM Torsten Bronger <bro...@physik.rwth-aachen.de> wrote:
Then, all boils down to the fact that you can’t pass []float64 as an
[]any.  To be honest, I still don’t fully understand why this is
forbidden

What would this do?

func F(s []any) {
    s[0] = "Foo"
}
func main() {
    s := []int{1,2,3,4}
    F(s)
    fmt.Println(s)
}

 
, so I just accept that the language does not allow it.

Thanks to both of you!

Regards,
Torsten.

--
Torsten Bronger

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Torsten Bronger

unread,
Oct 12, 2023, 1:31:50 AM10/12/23
to golan...@googlegroups.com
Hallöchen!

'Axel Wagner' via golang-nuts writes:

> [...]
>
> What would this do?
>
> func F(s []any) {
>     s[0] = "Foo"
> }
> func main() {
>     s := []int{1,2,3,4}
>     F(s)
>     fmt.Println(s)
> }

I think most intuitive would be if this behaved as an implicit
instantiation of the function with the type passed to it.

If I replace the signature of F with “F[T []E, E any](s T)”, the
compiler complaints “IncompatibleAssign” at “s[0] = "Foo"”. I can
do “fmt.Println(s[0])”, though. This is what somebody coming to Go
freshly would expect. (IMO)

Kurtis Rader

unread,
Oct 12, 2023, 1:49:29 AM10/12/23
to golan...@googlegroups.com
On Wed, Oct 11, 2023 at 10:31 PM Torsten Bronger <bro...@physik.rwth-aachen.de> wrote:
'Axel Wagner' via golang-nuts writes:

> [...]
>
> What would this do?
>
> func F(s []any) {
>     s[0] = "Foo"
> }
> func main() {
>     s := []int{1,2,3,4}
>     F(s)
>     fmt.Println(s)
> }

I think most intuitive would be if this behaved as an implicit
instantiation of the function with the type passed to it.

No. That is a recipe for disaster. You are arguing that container types (such as slices or maps) should be automatically converted. Such as is done by the C language for primary types (e.g., char, int, etc.). But even C does not allow implicitly converting one array type to another. You have a misunderstanding of what `any` means. The `any` keyword is a shorthand for `interface{}`. Any concrete type such as `int` is not equivalent to `interface{}`. Which means that a slice of a non `any` type cannot be magically converted to a slice of `interface{}`.

Torsten Bronger

unread,
Oct 12, 2023, 2:31:43 AM10/12/23
to golan...@googlegroups.com
Hallöchen!

Kurtis Rader writes:

> On Wed, Oct 11, 2023 at 10:31 PM Torsten Bronger <
> bro...@physik.rwth-aachen.de> wrote:
>
> 'Axel Wagner' via golang-nuts writes:
>
> > [...]
> >
> > What would this do?
> >
> > func F(s []any) {
> >     s[0] = "Foo"
> > }
> > func main() {
> >     s := []int{1,2,3,4}
> >     F(s)
> >     fmt.Println(s)
> > }
>
> I think most intuitive would be if this behaved as an implicit
> instantiation of the function with the type passed to it.
>
> No. That is a recipe for disaster. You are arguing that container
> types (such as slices or maps) should be automatically converted.

I am not sure that you understood me correctly. Anyway, I mean
instantiation of F with a concrete type before the actual
compilation step. The same thing already happens if you write F[T
[]E, E any](s T).

Patrick Smith

unread,
Oct 12, 2023, 2:52:16 AM10/12/23
to golan...@googlegroups.com
On Wed, Oct 11, 2023 at 11:31 PM Torsten Bronger <bro...@physik.rwth-aachen.de> wrote:
>     'Axel Wagner' via golang-nuts writes:
>   
>     > [...]
>     >
>     > What would this do?
>     >
>     > func F(s []any) {
>     >     s[0] = "Foo"
>     > }
>     > func main() {
>     >     s := []int{1,2,3,4}
>     >     F(s)
>     >     fmt.Println(s)
>     > }
>   
>     I think most intuitive would be if this behaved as an implicit
>     instantiation of the function with the type passed to it.

Anyway, I mean
instantiation of F with a concrete type before the actual
compilation step.  The same thing already happens if you write F[T
[]E, E any](s T).

Here F is a simple, non-generic function, with no type parameters. It cannot be instantiated. Any attempt to explain what this code would do if it were legal (which it should not be) should not involve generics, type parameters, or instantiation.

This is very different from what you would get by writing F[T []E, E any](s T) or F[E any](s []E); those are generic functions that can be instantiated. 

Torsten Bronger

unread,
Oct 12, 2023, 4:11:50 AM10/12/23
to golan...@googlegroups.com
Hallöchen!

Patrick Smith writes:

> [...]
>
> Here F is a simple, non-generic function, with no type
> parameters. It cannot be instantiated. Any attempt to explain what
> this code would do if it were legal (which it should not be)
> should not involve generics, type parameters, or instantiation.

Yes. I don’t suggest to change anything in Go. I only explained
what I had expected by theorising about a different compiler
behaviour.

That []float64 cannot be passed to []any is not intuitive (at least
in my brain FWIW), but it is a lesser-evil compromise the language
makes, as any programming language must make many compromises.
Reply all
Reply to author
Forward
0 new messages