Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: relax structural matching for conversions #19778

Open
luna-duclos opened this issue Mar 30, 2017 · 10 comments

Comments

@luna-duclos
Copy link

commented Mar 30, 2017

Currently, as of Go 1.8, one can convert between two identical types, A and B, defined as follows:

type A struct {
	Foo int
}

type B struct {
	Foo int
}

It is however not possible to convert between []A and []B, even though the memory layout of both slices is identical, and one has to resort to following hackery to get both compile time checking and O(1) conversion time:

package main

import (
	"fmt"
	"unsafe"
)

type A struct {
	Foo int
}

type B struct {
	Foo int
}

// Compile time check that A and B are the same
var _ = A(B{})

func main() {
	a := A{Foo: 5}
	fmt.Println(B(a))
	
	as := []A{{Foo: 5}}
	fmt.Println(*(*[]B)(unsafe.Pointer(&as)))
}

This is often useful when different parts of an application are responsible for different parts of the handling of the lifetime of an object. For example, a database layer could return []A, which would be a type with db:"foo" decorators to help with database deserialization using the sqlx package, while the
http handler wants to use []B, because it contains the field tags necessary for json serialization.

Currently, a copy or unsafe is required, I propose allowing slices with identical memory layouts to be converted between another, and to restrict this to only 1 level deep to reduce possible complexity in the compiler.

This is a similar reasoning why the spec was changed to allow structs with different field tags to be converted between eachother, and I believe it would help avoid usage of unsafe and copies in many applications which seperate out concerns between different layers.

@luna-duclos luna-duclos changed the title Allow typecasting of identical slices with different types Allow conversion of identical slices with different types Mar 30, 2017

@luna-duclos luna-duclos changed the title Allow conversion of identical slices with different types proposal: spec: Allow conversion of identical slices with different types Mar 30, 2017

@gopherbot gopherbot added this to the Proposal milestone Mar 30, 2017

@gopherbot gopherbot added the Proposal label Mar 30, 2017

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Mar 30, 2017

@ericlagergren

This comment has been minimized.

Copy link
Contributor

commented Apr 2, 2017

This is something I've always found a little counterintuitive, especially if the underlying types are the same. I can think of a couple of times I've had to write my own strings.Join routines because the alternative is to copy the entire slice. e.g., https://github.com/ericlagergren/decimal/blob/522450d1e655d86ef13b0bfad42d090a0240ce03/suite/suite.go#L73

It seems to make sense that if T1 and T2 are slices whose elements are all the same underlying types, they should be convertible without an allocation.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Apr 3, 2017

@luna-duclos Note that for struct field tags, conversion is ignoring the tags "all the way down" - not just one level deep. That would be something to consider if we were to make this change. (Though this is not going to happen for Go 1.0 I suspect).

@rsc rsc added the Go2 label Apr 3, 2017

@rsc rsc changed the title proposal: spec: Allow conversion of identical slices with different types proposal: spec: relax structural matching for conversions Jun 16, 2017

@pebbe

This comment has been minimized.

Copy link
Contributor

commented Jul 22, 2017

This goes for non-struct types as wel.
If you have this:

type MyType int

You can't do this:

a := []int{1, 2, 3}
b := []MyType(a)

Instead, you have to do this:

a := []int{1, 2, 3}
b := make([]MyType, len(a))
for i, v := range a {
    b[i] = MyType(v)
}

Making b := []MyType(a) possible would perhaps eliminate the desire for generics in some cases?

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Jan 30, 2018

Seems related to #15209, which is about append and copy.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Jan 30, 2018

As I understand it, I think this proposal is asking for a concept that the language does not currently have: we permit converting []A to []B when A and B have identical underlying types ignoring field tags and type names, recursively (and this actually applies to all composite types, not just slices).

That can't really be the rule, though, because it breaks for interface types. Consider

type X int
type Y1 interface { M(X) }
type Y2 interface { M(int) }
var V1 []Y1
var V2 []Y2

Can I write V2 = ([]Y2)(V1)? According to the rule I wrote above, I can, because the interface types are identical ignoring type names. But this conversion can't be right, because the set of types that implement Y1 is disjoint from the set of types that implement Y2.

So the conversion rule must be something more complex than I wrote above. What is it?

@vvoronin

This comment has been minimized.

Copy link

commented Apr 3, 2018

It can be done by adding a type convertion check along with underlying type equality.
If we formulate the rule like this:
"permit converting composite types like []A to []B when A and B have identical underlying types ignoring field tags and type names and can be type converted, recursively"

@gopherbot

This comment has been minimized.

Copy link

commented Apr 9, 2018

Change https://golang.org/cl/105937 mentions this issue: reflect: prevent conversion of invariant slices

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Apr 9, 2018

@vvoronin Thanks, but I'm not sure I understand your adjusted rule. Clearly it can't apply at top level, since at top level the types can not be converted. So, when does it apply?

@vvoronin

This comment has been minimized.

Copy link

commented Apr 10, 2018

@ianlancetaylor
Oh, you are right, sorry for inconvenience, my english is quite bad.

I meant "when simple non-composite type can be type converted and have have identical memory layout, composite of that type also must be able to be type converted"

For example:

type A struct {   A string  }
type B struct {   A string  }
// we can convert this
var varA A = A(B{})
//  and A and B have identical memory layout, so
var sliceOfB []B = []B([]A{})
type Y1 interface { M(X) }
type Y2 interface { M(int) }
var V1 Y1
var V2 Y2
// we cannot convert this
V2 = Y2(V1)
// so we cannot convert a slice or map of this
// we can convert this
byteArray := []byte(`string`)
// but memory layout is different
// so we cannot convert a slice or map of this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.