Why is there no variability in the Go type system?

Source: Internet
Author: User
* * This article explains what covariance, contravariance, and invariance are, and how it affects the Go type system. In particular, it explains why there is no possibility of variability in slices. * * A Go beginner often asks the question "Why can't I pass the ' []int ' type variable to the function ' func ([]interface{}) '?" In this article, I want to explore this issue and its impact on Go. But the concept of variability (described in this article) is also useful in other languages. Variability describes what happens when a subtype relationship is used in a composite type. In this case, "A is a subtype of B" means that an instance of type A can always be used as a scenario that requires type B. Go does not have a definite subtype relationship, and the closest thing to it is the assignable nature, which determines whether the type can be used interchangeably. The interface may be the most important usage scenario: if the type T (whether it is a concrete type or an interface itself) implements interface I, then T can be considered a subtype of I. In this sense, ' *bytes. Buffer ' is ' io. Readwriter ' sub-type, ' IO. Readwriter ' is ' io. The subtype of Reader '. All types are sub-types of ' interface{} '. The simplest way to understand the meaning of variability is to look at the function type. Suppose we have a type and a subtype, such as ' *bytes. Buffer ' is ' io. The subtype of Reader '. You can define such a function ' func () *bytes. Buffer '. We can also use this function as ' func (') Io. Reader ', we just redefine the return value as ' io '. Reader '. But the opposite direction is not tenable: we cannot put the function ' func () io. Reader ' as function ' func () *bytes. Buffer ', because not every ' IO. Reader ' can be ' *bytes '. Buffer '. Therefore, the function return value can maintain the direction of the subtype relationship: If A is a subtype of B, then the function ' func () A ' can be a subtype of function ' func () B '. This is called covariant. "' Gofunc F () io. Reader {return new (bytes. Buffer)}func G () *bytes. Buffer {return new (bytes. Buffer)}func use (f func () Io. Reader) {Usereader (f ())}func main () {use (f)//Works use (G)//DoeSN ' t work right now; But *could* is made equivalent to ... Use (func () io. Reader {return G ()}} "" On the other hand, suppose we have the function ' func (*bytes. Buffer) '. Now we can't think of it as function ' func (IO. Reader) ', you can't use ' IO. Reader ' as a parameter to invoke it. But we can call it in the opposite direction. If we use ' *bytes. Buffer ' as a parameter, you can use it to call ' func (IO. Reader) '. Therefore, the parameters of the function reverse the subtype relationship: If A is a subtype of B, then ' func (b) ' can be a subtype of ' func (A) '. This is called contravariance. "' Gofunc F (r io. Reader) {Usereader (R)}func G (R *bytes). Buffer) {Usereader (R)}func use (f func (*bytes). Buffer) {b: = new (bytes). Buffer) F (b)}func main () {use (f)//doesn ' t work right now, but *could* is made equivalent to ... Use (func (R *bytes). Buffer) {F (R)}) use (G)//Works} "" Therefore, ' func ' is the inverse value for the parameter and is covariant for the return value. Of course, we can combine these two properties: if A and C are sub-types of B and D respectively, we can make ' func (B) C ' A subtype of ' func (A) D ', which can be transformed: ' ' go//*os. Patherror implements Errorfunc F (R io. Reader) *os. Patherror {//...} Func use (f func (*bytes). Buffer) {b: = new (bytes). Buffer) Err: = f (b) useerror (ERR)}func main () {use (f)//Could is made to is equivalent to use (func (R *bytes). Buffer) "Error {return F (R)})} '", however, ' func (A) C' and ' func (B) D ' are incompatible. One cannot be a subtype of another. "' Gofunc F (R *bytes. Buffer) *os. Patherror {//...} Func Usef (f func (IO). Reader) (Error) {b: = strings. Newreader ("Foobar") Err: = f (b) useerror (ERR)}func G (R io. Reader) Error {//...} Func useg (f func (*bytes). Buffer) *os. Patherorr) {b: = new (bytes. Buffer) Err: = f () Usepatherror (Err)}func main () {USEF (f)//Can ' t work, Because:usef (func (R io). Reader) Error {return F (R)//Type-error:io. Reader is not *bytes. Buffer}) useg (G)//Can ' t work, Because:useg (func (R *bytes). Buffer) *os. Patherror {return G (R)//Type-error:error is not *os. Patherror})} ' Therefore, in this case, there is no relationship between the composite types. This is called invariance. Now, we can return to our question: Why can't we use ' []int ' as ' []interface{} '? This is actually asking: "Why is the ' slices ' type constant"? The questioner assumes that ' int ' is a subtype of ' interface{} ', so ' []int ' should also be a subtype of ' []interface{} '. However, we can now look at a simple question. The ' slices ' support (among other things) two basic operations, we can roughly convert to function call: "' Goas: = make ([]a, x) A: = As[0]//func Get (as []a, I int) aas[1] = A//func Set (as []a, I int, a a) "This clearly has a problem: type A appears as a parameter and as a return type. Therefore, it has both covariance and inversion. Therefore, when the function is called, there is a relativeClear answers to explain how variability works, it just doesn't make much sense for ' slices '. Reading ' slices ' requires covariance, but writing ' slices ' requires contravariance. In other words, if you need to make ' []int ' a subclass of ' []interface{} ', you need to explain how this code works: ' Gofunc G () {V: = []int{1,2,3} F (v) fmt. Println (v)}func F (v []interface{}) {//string is a subtype of interface{}, so the should be valid v[0] = "Oops"} "' Chan Nel ' provides another interesting perspective. The bidirectional ' channel ' type has the same problem as the ' slices ' type: it needs to be covariant when it is received, and it needs to be reversed when it is sent. But you can restrict the direction of the ' channel ' and allow only send or receive operations. So ' Chan A ' and ' Chan B ' can have no relationship, we can make ' <-chan a ' a subclass of ' <-chan B ', or ' chan<-b ' become a subclass of ' chan<-a '. In this sense, the read-only type at least in theory can allow the variability of ' slices '. ' []int ' is still not a subtype of ' []interface{} ', we can make ' ro[] int ' A sub-type of ' ro []interface ' (borrowed from the syntax in proposal). Finally, I would like to emphasize that all of this is simply a matter of theoretically adding variability to the Go type system. I think it is difficult, but even if we can solve these problems, we will still encounter some practical problems. The most pressing of these is the different memory structure of the subtypes: "' Govar (//Super pseudo-code to illustrate X *bytes. Buffer//unsafe. Pointer y io. Readwriter//struct{itable *itab; value unsafe. Pointer}//Where itable has both entries Z io. Reader//struct{itable *itab; value unsafe. Pointer}//Where itable has one entry) ' So even if you think allInterfaces have the same memory model, they actually do not, because the method table has a different assumed layout. So in this code, ' Gofunc Do (f func () Io. Reader) {r: = f () r.read (BUF)}func f () io. Reader {return new (bytes. Buffer)}func G () io. Readwriter {return new (bytes. Buffer)}func H () *bytes. Buffer {return new (bytes. Buffer)}func Main () {//All of F, G, H should is subtypes of func () Io. Reader do (F) does (G) do (h)} "also needs to be returned to the ' IO ' in a certain place. Readwriter ' interface is packaged as ' IO. Reader ' interface, and need to place G's return ' *bytes somewhere. Buffer ' can be converted to the correct ' io. Reader ' interface. This is not a big problem for a function: The compiler can generate the appropriate wrapper when the ' main ' function is called. There is a certain performance overhead when using this form of subtype in your code. However, this is a very important issue for ' slices '. We have two ways of dealing with ' slices '. (a) the transfer of ' []int ' to ' []interface{} ' means a distribution and a complete copy. (b) Delay the conversion of ' int ' and ' interface{} ' until it is required for access. This means that every ' slices ' access must now be called through an indirect function, just in case someone passes us a subtype. Neither of these options fits the Go design goal.

Via:https://blog.merovius.de/2018/06/03/why-doesnt-go-have-variance-in.html

Author: Axel Wagner Translator: Althen proofreading: Rxcai

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

254 Reads
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.