-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: allow a slice of an impl. type to be the same as a slice of the interface #21651
Comments
One major problem is that The problem you are seeing is a widely studied topic called the covariance and contravariance of types and leads to many subtle and non-intuitive outcomes.
This is not entirely accurate. Other languages have subtle restrictions and side-effects when allowing this. Go simply chose to avoid the problem altogether (i.e., This problem is heavily related to the topic of generics, and we will most likely not address this until Go2, and even then, what happens here is going to be heavily driven by what (if anything) happens with generics. |
Sorry, hit the wrong button. |
I like the proposal. From logical perspective, as a developer, I expect this code to work: type (
Person interface {
GetName() string
}
type User struct {
name string
}
type Admin struct {
User
}
)
func (u *User) GetName() string {
return u.name
}
func printNames(people []Person) {
for _, person := range people {
fmt.Println(person.GetName())
}
}
func main() {
printNames([]Person {
&User{ name: "foo" },
&User{ name: "bar" }
})
printNames([]Person {
&Admin{ name: "foo" },
&Admin{ name: "bar" }
})
} And since, it does not work, means that we have classic leaking abstraction and less flexible interfaces. |
@ziflex: That code does work fine, modulo a few typos and issue #9859. package foo
import "fmt"
type (
Person interface {
GetName() string
}
User struct {
name string
}
Admin struct {
User
}
)
func (u *User) GetName() string {
return u.name
}
func printNames(people []Person) {
for _, person := range people {
fmt.Println(person.GetName())
}
}
func main() {
printNames([]Person {
&User{ name: "foo" },
&User{ name: "bar" },
})
printNames([]Person {
&Admin{ User{name: "foo"} },
&Admin{ User{name: "bar"} },
})
} |
... and I just learned that putting "go" after the triple backquote enables Go syntax highlighting! |
@randall77 my bad! too much of copy paste. package foo
import "fmt"
type (
Person interface {
GetName() string
}
User struct {
name string
}
Admin struct {
User
}
)
func (u *User) GetName() string {
return u.name
}
func printNames(people []Person) {
for _, person := range people {
fmt.Println(person.GetName())
}
}
func main() {
printNames([]*User {
&User{ name: "foo" },
&User{ name: "bar" },
})
printNames([]*Admin {
&Admin{ User{name: "foo"} },
&Admin{ User{name: "bar"} },
})
} Even though, these types both implement the interface due to technical details you cannot use them in a such way. |
Right, the issue is that using |
@randall77 so, that's what I'm talking about - it's a pretty common use case of interfaces utilization. The most straightforward workaround is to create I saw the comment and read the explanation before, but still not convinced that it's ok. |
Are you suggesting that Go automatically create a copy of |
As I pointed out in my first comment. This is not true. Covariance and contravariance are real topics in type theory, and you can create a mapping of how those theories applies to Go's type system (especially in regard to concrete types and interfaces). |
A If your
|
If it's implemented by pointer copying, there are still issues: func SwapPersons(ps []Person, i, j int) {
ps[i], ps[j] = ps[j], ps[i]
} This function would not work with the pointer copying solution since And copying pointers is certainly cheaper than copying the entire struct, but it is still an expensive operation since it is |
I understand the annoyance of doing this. I have struggled with this myself this past weekend, but I'm not convinced the type system is where this should be "fixed". #15209 is probably a better solution to this problem. Since a printNames(append([]Person{}, users...)) // With #15209
printNames([]Person{users...}) // With #15209 and #19218 This is more verbose than having an "implicit conversion", but at least it makes it obvious an allocation is occurring and avoids magic. |
you may write the following (is shorter an works right now): package foo
import "fmt"
type (
Person interface {
GetName() string
}
User struct {
name string
}
Admin struct {
User
}
)
func (u *User) GetName() string {
return u.name
}
func printNames(people ...Person) {
for _, person := range people {
fmt.Println(person.GetName())
}
}
func main() {
printNames(
&User{ name: "foo" },
&User{ name: "bar" },
)
printNames(
&Admin{ User{name: "foo"} },
&Admin{ User{name: "bar"} },
)
} |
Adding covariance to the language requires more discussion than this. Currently function calls work by assignment, so this proposal is presumably also changing assignment. It is a change that affects several aspects of the language. It presumably introduces hidden loops and raises the problem mentioned above, of changing elements in a copied slice. This proposal can not be fully analyzed as is, and is unlikely to be accepted in any case, so closing. |
Problem
One function has to be programmed which works by iterating over a slice of the interface type
AccessControlledEntity
which tells whether all objects pass the access flag criteria:When having a type
A
which implements an interfaceI
, a slice of typeA
can not be passed as a slice of typeI
to a function. In my use case I have a typeDocument
which holds an ACL and implements the interface typeAccessControlledEntity
. Another type calledFolder
also implements the interface typeAccessControlledEntity
.Go does does not allow a slice of
[]Document
or[]Folder
to be passed to the function. The wiki article here describes the problem but makes the assumption that one should actually manually create a copy of the slice which is the concrete type[]AccessControlledEntity
(or whatever the type might be).This language restraint therefore wants me to write for each different concrete type a separate function, which does the exact same thing:
In my humble opinion a programming language should try to reduce redundancy in code but this language spec. is the exact contrary. The compiler acts as if it almost knows that it is wrong, since converting a
*Folder
to anAccessControlledEntity
works without any problems. The last piece in the puzzle would be the slice conversion to work seamlessly.This becomes even worse when a function returns a slice of the interface type:
Now each type for which we want to use the function, has to re-implement the same "unpack" function:
If I understand the wiki article correctly the runtime overhead is always there, whether I manually write X times the same for loops or if the compiler auto generates the same code under the hood. However one is manual work with no benefits and the other is the work a compiler should do anyways. In countless other programming languages this is a non existing problem and it hinders me to really enjoy Go, because this restraint seems to be so implausible.
Proposal
Note: This "proposal" does not explain how to solve the problem but just describes in a declarative way, what should be fixed.
Allow in the Go language, that a slice of an implementing type can be passed as a slice of the implemented interface type to a function. Manual writing of for loops which copy the slice to or from the other type should not be necessary.
This should work:
The text was updated successfully, but these errors were encountered: