admin管理员组

文章数量:1123672

Let's say we have two named types that derive from int:

type Number1 int
type Number2 int

Since their underlying type is the same, they are assignable to each other, so this works

var x Number1 = 42
var y Number2 = Number2(x)

However, if we have slices, they're not directly assignable:

sl1 := []Number1{1, 2, 3}
sl2 := []Number2(sl1) // doesn't work

But we can just make a copy of the slice:

sl2 := make([]Number2, len(sl1))
for i, v := range sl1 {
    sl2[i] = Number2(v)
}

We can even define a generic function over ints to do the conversion:

func Convert[To ~int, From ~int](from []From) []To {
    to := make([]To, len(from))
    for i, v := range from {
        to[i] = To(v)
    }
    return to
}

and use it like that: var sl2 []Number2 = Convert[Number2](sl1). This works fine.

My question is: is there a way to make Convert work with any 2 generic types From/To (not just types whose underlying type is int) as long as From is assignable to To?

I've tried a few things but I've not been able to make it work. Basically what I want is something like

func Convert[To ~E, From ~E, E any](from []From) []To { // invalid!
    ...
}

except this is not allowed because E cannot be a type parameter in ~E.

So, is there a solution? If yes, please provide an example, if no, please explain why it's not possible.

Let's say we have two named types that derive from int:

type Number1 int
type Number2 int

Since their underlying type is the same, they are assignable to each other, so this works

var x Number1 = 42
var y Number2 = Number2(x)

However, if we have slices, they're not directly assignable:

sl1 := []Number1{1, 2, 3}
sl2 := []Number2(sl1) // doesn't work

But we can just make a copy of the slice:

sl2 := make([]Number2, len(sl1))
for i, v := range sl1 {
    sl2[i] = Number2(v)
}

We can even define a generic function over ints to do the conversion:

func Convert[To ~int, From ~int](from []From) []To {
    to := make([]To, len(from))
    for i, v := range from {
        to[i] = To(v)
    }
    return to
}

and use it like that: var sl2 []Number2 = Convert[Number2](sl1). This works fine.

My question is: is there a way to make Convert work with any 2 generic types From/To (not just types whose underlying type is int) as long as From is assignable to To?

I've tried a few things but I've not been able to make it work. Basically what I want is something like

func Convert[To ~E, From ~E, E any](from []From) []To { // invalid!
    ...
}

except this is not allowed because E cannot be a type parameter in ~E.

So, is there a solution? If yes, please provide an example, if no, please explain why it's not possible.

Share Improve this question asked 21 hours ago GohuGohu 2,1072 gold badges18 silver badges19 bronze badges 3
  • 2 I think the answer is "no", and you also slices aren't needed to exhibit the problem Convert[To ~E, From ~E, E any](from From) To { return To(from) } -- is also impossible. – Paul Hankin Commented 20 hours ago
  • You can, of course pass in a function that converts From to To, and have the function work for any types -- this is usually called "Map". – Paul Hankin Commented 20 hours ago
  • 2 Relevant: github.com/golang/go/issues/71183 – Paul Hankin Commented 20 hours ago
Add a comment  | 

1 Answer 1

Reset to default -4

You could do this using reflection (Go Playground):

func Convert[To, From any](from []From) []To {
    f := reflect.TypeFor[From]()
    t := reflect.TypeFor[To]()
    if !f.ConvertibleTo(t) {
        return nil
    }

    to := reflect.MakeSlice(reflect.SliceOf(t), len(from), len(from))
    for i, v := range from {
        to.Index(i).Set(reflect.ValueOf(v).Convert(t))
    }

    return to.Interface().([]To)
}

The disadvantage is that it wouldn't be type safe and checked only during run time.

You can't force this in a generic function API, since the Go specification states:

In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface.

and

The type T in a term of the form T or ~T cannot be a type parameter, ...

So, [To ~E, From ~E, E any], is an invalid expression, since E is a type parameter.

The rationale it that the compiler can provide specialized code for concrete types, while it would have to use reflection in the general case, so you would end up with the above solution.

A way to have a little more type safety (but not a function signature) would be:

    sl2 := unsafe.Slice((*Number2)(unsafe.SliceData(sl1)), len(sl1))

using unsafe.Slice, referencing the original array. This is type checked, since:

A non-constant value x can be converted to type T in any of these cases:

... x's type and T are pointer types that [...], and their pointer base types [...] have identical underlying types.

本文标签: goIs a generic slice conversion function possibleStack Overflow