-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: Go 2: permit conversion from []chan int to []<-chan int #41695
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
Comments
What you want is covariance, where if X and Y are assignable, []X and []Y also become assignable ti each other. https://en.m.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) In the past the Go authors have repeatedly stated that they want to keep the type system simple, and don't want to introduce covariance. Generics will help a bit here, though, it will likely be possible to have a generic function like that does the copying conversion. |
It's true that Go has so far rejected covariance, but this does seem to be a special case: a case of adding (or, presumably, removing) type qualifiers, rather than changing types in a more general way. In particular there is no question here of changing the method set, which is one of the reasons for rejecting covariance (https://golang.org/doc/faq#convert_slice_with_same_underlying_type). |
Well, if you are willing to consider this special case, then simply allowing a type cast ([]<-chan int)(chans), and presumably also (<-chan int)(ch) would be a backwards compatible feature. |
Note that |
Yes, that's correct. However, I am not really convinced that the argument about changing the method set is very convincing. Already, Go allows certain casts where the method set changes, but not for others with the built in collections of these types. https://play.golang.org/p/8Pfni3CMXvS package main
import (
"fmt"
)
type MyInt int
type MyIntChan chan MyInt
type ROMyIntChan <-chan MyInt
type IntChan chan int
type ROIntChan <-chan int
type ArrIntChan []chan int
type ArrROIntChan []<-chan int
type ArrMyIntChan []chan MyInt
func main() {
var ch1 chan int
ch2 := (<-chan int)(ch1) // This does not change the method set.
ch3 := IntChan(ch1) // This changes the method set.
ch4 := ROIntChan(ch1) // This changes the method set.
// ch5 := MyIntChan(ch1) // Not allowed
fmt.Printf("%v, %v, %v, %v\n", ch1, ch2, ch3, ch4)
var i1 int
i2 := MyInt(i1) // This changes the method set.
fmt.Printf("%d, %d\n", i1, i2)
var a1 []int
// a2 := ([]MyInt)(a1) // Not allowed.
fmt.Printf("%v\n", a1)
var cha1 []chan int
//cha2 := ([]<-chan int)(cha1)// Not allowed
cha3 := ArrIntChan(cha1) // This changes the method set.
//cha4 := ArrROIntChan(cha1) // Not allowed
//cha5 := ArrMyIntChan(cha1) // Not allowed
fmt.Printf("%v\n", cha3)
} Probably similar for maps. Casts that would change the method set of the element of a array, slice, map, or channel are not allowed, but changing the method set for the whole collection, as well as adding a channel restriction to make it read only or write only are allowed. Perhaps some of these other restrictions could be loosened, especially if no change in the underlying type occurs? As a chan MyInt has the same layout in memory as a chan int, and similarly for maps, slices and arrays of MyInt versus those with int? |
Let's please not sidetrack this specific proposal into a general one about converting slices of elements with the same underlying type. Thanks. I'm just pointing out that this specific proposal is not quite like the general case, because it involves only changing type qualifiers in the slice elements. Go has exactly one type qualifier, channel direction, and perhaps it is OK to permit conversions of slices that only change the type qualifier in the slice element type. Or perhaps not, I'm not sure. My only point is that this is different from the general case, in a way that may be relevant. |
OK, I agree that a more general proposal would be a separate issue. However, the point I was trying to make was that Go, as it is now, is consistent in that casts between collections of types that have the same underlying type are not allowed. That's why I thing that an exception just for read only channels would make Go harder to learn with limited benefits. |
The debate about I'm not sure this is an exception about the casting of underlying type, for me About the consistency, what I suggests is to make the same behaviour for all type qualifier (even if as @ianlancetaylor noticed, it only exists channel direction actually). This is a subject to debate with the community. Because, of course, people will think that underlying type casting should be accepted if that specific case here is accepted. |
This proposal suggests allowing
But what about
I think we don't want to allow the latter. So it isn't just about changing type qualifiers. We need the additional restriction that the cast is changing qualifiers in the direction that restricts the allowed operations. As a separate thought, this proposal assumes that |
In the other direction: if we are going to assume that |
I think the actual type qualifier casting behaviour must stay the same, to remind (where
To resume: we can add a type qualifier but we can't change it or remove it. We can add this behaviour to different things, but now, we can talk about underlying type qualifier casting. SlicesI assume we can add type qualifier casting to slices (not included options are not allowed) :
MapsI assume we can add type qualifier casting to maps (not included options are not allowed) :
Weird but maybe:
FunctionsI assume we can also add type qualifier casting to functions (not included options are not allowed) :
Not totally sure for functions to be honest 🤔 It's pretty weird here, no ? ChannelsI assume we can also add type qualifier casting to channels (not included options are not allowed) :
PointersI assume we can also add type qualifier casting to pointers (not included options are not allowed) :
|
I think from the above we can see this is getting complicated, with limited benefits. As an aside, it also makes me wonder if we should perhaps consider allowing type qualifiers on all types, <-T for a read only T and T<- for a write only T. But I digress... |
This is getting complicated... yes and no, because it's for all the possible cases, not an exception to slices. ^^ This doesn't not apply to all types. Only "containers" to the underlying type qualifier. Btw, this was an idea... It require to be validated by the community. ;) |
This proposal is impossible without introducing runtime panics, as adding covariance to a collection in OOP will restrict the operations which you can do on that collection. There is no way of doing this in Go. The following functions seem innocent: func SetPointer(assign *<-chan int, value <-chan int) {
*assign = value
}
func FillSlice(slice []<-chan int, value <-chan int) {
for i := range slice {
slice[i] = value
}
} Until we realize that they make this possible: func main() {
ch := make(chan int)
readCh := (<-chan int)(make(chan int))
// we assign ch, type `chan int`, a value of type `<-chan int`.
// this cannot be allowed.
SetPointer(&ch, readCh)
// and same issue with slices:
chSlice := make([]chan int, 10)
FillSlice(chSlice, readCh)
} In languages like Java, adding covariance to a class's generic type parameter will limit the methods which you can use with it. It adds a ton of complications, and gets very confusing. We should really avoid adding this to Go in my opinion. |
I'd also like to point out that allowing func ReadFirst(slice []chan int) chan int {
return slice[0]
} Because, of course, passing in a slice of |
To give an idea of how this issue is solved in Java: // in the following examples, E is the element type of the list, and V is the type of some
// variable (whatever is being assigned to/from the list)
// safe: we can pass any List<E> such that E extends V,
// meaning that E is always assignable to V
public static <E extends V, V> V getFirst(List<E> list) {
return list.get(0)
}
// safe: we can pass any List<E> such that E is a superclass of V,
// so we know V is always assignable to E
public static <E super V, V> void setFirst(List<E> list, V v) {
return list.set(0, v)
}
// compile error, as E is a superclass of V, so E is not
// assignable to V. (aka Gopher g = animalList.get(0); is not valid)
public static <E super V, V> V getFirst(List<E> list) {
return list.get(0)
}
// compile error, as E is a subclass of V, so V is not
// assignable to E. (aka gopherList.set(0, animal); is not valid)
public static <E extends V, V> void setFirst(List<E> list, V v) {
return list.set(0, v)
} |
Hello @deanveloper
FIrst, we don't talk about adding covariance. We're not talking either about adding runtime panics because it's not really in the mindset of Go. And finally Go is not OOP.
We absolutely not talking about adding covariance... Furthermore, types are not type qualifiers (
I never talk about allowing Finally, I don't understand why you talk about Java. Go and Java are two totally different languages. Your last snippet show that you don't understand that type qualifiers are not types, it's a Go specific feature which I don't see in any other languages. Anyway, you pointed out a really good example where the type qualifier casting might create issues:
Here, the function looks good, but the automatic casting from |
Hugh... It seems that there is some another BIG exceptions... package main
import (
"fmt"
)
func SetSlice(assign []<-chan int, value <-chan int) {
assign[0] = value
}
func main() {
ch := []chan int{make(chan int)}
readCh := (<-chan int)(make(chan int))
SetSlice(ch, readCh)
} Then come another one idea: (my concrete example why we should add that) func f() []<-chan int {
chans := []chan int{make(chan int)}
go func(){
time.Sleep(1000)
chans[0]<-42 // Error: chans[0] is now of type `<-chan int`
}()
return chans
}
func main() {
chans := f()
ch := make(<-chan int)
chans[0] = ch // Error: chans[0] is of type `chan int` in the function f()
time.Sleep(1500)
} This example show that in term of types, both Soooo... Unfortunately, we can't add underlying type qualifier casting to Go because it may add more and more issues. At least if someone else has an idea to counter that both issues. Adding "runtime panics" may fix that but it's absolutely not in the Go philosophy. Adding const/unmodifiable variables may resolves issues but it's another subject.. |
@scorsi, @deanveloper is referring to Java because Java has covariant arrays, and as a result has a run-time And I agree with @deanveloper that this conversion cannot be allowed safely. (You can already do it unsafely using |
Ok now I see the relationship with Java. But Go can't add panics when settings a slice element because a slice is a type and there is no differences between
@bcmills which conversion cannot be allowed safely ? Btw, there are a lot of deep potentials issues when allowing underlying type qualifier casting. |
We could, however, allow the Today they fail with: However, there is no fundamental reason why they need to do so, given that it is possible for user code to implement exactly the same copying and appending behavior: https://play.golang.org/p/DCNKeILHQEV Perhaps I will file that as a separate proposal. |
@scorsi: we cannot safely allow any conversion from a slice of (or pointer to) Pointers and slices cannot be covariant, because the type itself cannot distinguish between inputs (writes) and outputs (reads). In contrast, functions can (at least theoretically) be covariant, because the inputs (arguments) and outputs (return-values) in the function signature are distinct. |
Yes ! This is a really good idea here. Allowing those both functions might do the trick. If we edit the copied slice we don't change the original slice but we can read (or write) from (or to) the original channel.
You can't append or set a |
Based on the discussion above, this is a likely decline. Leaving open for four weeks for final comments. |
No further comments. |
I would like to find a solution to #40010.
To remind the problem is:
Which result on a
Compile error: cannot use res (type []chan x) as type []<-chan x in return argument
.The only workaround is either
But it's not dev-friendly, what the dev have to do with a simple chan ? read or write ?.. This is what I choose actually.
Which is very compute-time consuming and not the right solution too.
The text was updated successfully, but these errors were encountered: