Golang-interface(三 接口类型)
Go Data Structures: Interfaces
Posted on Tuesday,December 1,2009.
Go's interfaces—static,checked at compile time,dynamic when asked for—are,for me,the most exciting part of Go from a language design point of view. If I could export one feature of Go into other languages,it would be interfaces. This post is my take on the implementation of interface values in the “gc” compilers: 6g,8g,and 5g. Over at Airs,Ian Lance Taylor has writtentwopostsabout the implementation of interface values in Before looking at the implementation,let's get a sense of what it must support. Usage Go's interfaces let you useduck typinglike you would in a purely dynamic language like Python but still have the compiler catch obvious mistakes like passing an type ReadCloser interface { Read(b []byte) (n int,err os.Error) Close() } and then define your new function as taking a func ReadAndClose(r ReadCloser,buf []byte) (n int,err os.Error) { for len(buf) > 0 && err == nil { var nr int nr,err = r.Read(buf) n += nr buf = buf[nr:] } r.Close() return } The code that calls Interfaces aren't restricted to static checking,though. You can check dynamically whether a particular interface value has an additional method. For example: type Stringer interface { String() string } func ToString(any interface{}) string { if v,ok := any.(Stringer); ok { return v.String() } switch v := any.(type) { case int: return strconv.Itoa(v) case float: return strconv.Ftoa(v,'g',-1) } return "???" } The value As a simple example,let's consider a 64-bit integer type with a type Binary uint64 func (i Binary) String() string { return strconv.Uitob64(i.Get(),2) } func (i Binary) Get() uint64 { return uint64(i) } A value of type These examples show that even though all the implicit conversions are checked at compile time,explicit interface-to-interface conversions can inquire about method sets at run time. “Effective Go” has more details about and examples of how interface values can be used. Interface ValuesLanguages with methods typically fall into one of two camps: prepare tables for all the method calls statically (as in C++ and Java),or do a method lookup at each call (as in Smalltalk and its many imitators,JavaScript and Python included) and add fancy caching to make that call efficient. Go sits halfway between the two: it has method tables but computes them at run time. I don't know whether Go is the first language to use this technique,but it's certainly not a common one. (I'd be interested to hear about earlier examples; leave a comment below.) As a warmup,a value of type Interface values are represented as a two-word pair giving a pointer to information about the type stored in the interface and a pointer to the associated data. Assigning The first word in the interface value points at what I call an interface table or itable (pronounced i-table; in theruntime sources,the C implementation name is The second word in the interface value points at the actual data,in this case a copy of To check whether an interface value holds a particular type,as in thetype switchabove,the Go compiler generates code equivalent to the C expression To call The example we're considering is an interface with just one method. An interface with more methods would have more entries in thefunlist at the bottom of the itable. Computing the Itable Now we know what the itables look like,but where do they come from? Go's dynamic type conversions mean that it isn't reasonable for the compiler or linker to precompute all possible itables: there are too many (interface type,concrete type) pairs,and most won't be needed. Instead,the compiler generates a type description structure for each concrete type like In our simple example,the method table for Memory OptimizationsThe space used by the implementation described above can be optimized in two complementary ways. First,if the interface type involved is empty—it has no methods—then the itable serves no purpose except to hold the pointer to the original type. In this case,the itable can be dropped and the value can point at the type directly: Whether an interface type has methods is a static property—either the type in the source code says Second,if the value associated with the interface value can fit in a single machine word,there's no need to introduce the indirection or the heap allocation. If we define Binary version far above,the method in the itable is(*Binary).String ,while in theBinary32 example,monospace">Binary32.Stringnot(*Binary32).String .
Of course,empty interfaces holding word-sized (or smaller) values can take advantage of both optimizations: Method Lookup PerformanceSmalltalk and the many dynamic systems that have followed it perform a method lookup every time a method gets called. For speed,many implementations use a simple one-entry cache at each call site,often in the instruction stream itself. In a multithreaded program,these caches must be managed carefully,since multiple threads could be at the same call site simultaneously. Even once the races have been avoided,the caches would end up being a source of memory contention. Because Go has the hint of static typing to go along with the dynamic method lookups,it can move the lookups back from the call sites to the point when the value is stored in the interface. For example,consider this code snippet: 1 var any interface{} // initialized elsewhere 2 s := any.(Stringer) // dynamic conversion 3 for i := 0; i < 100; i++ { 4 fmt.Println(s.String()) 5 } In Go,the itable gets computed (or found in a cache) during the assignment on line 2; the dispatch for the In contrast,the implementation of this program in a dynamic language like Smalltalk (or JavaScript,or Python,or ...) would do the method lookup at line 4,which in a loop repeats needless work. The cache mentioned earlier makes this less expensive than it might be,but it's still more expensive than a single indirect call instruction. More Information The interface runtime support is in Code Supporting code ( package main import ( "fmt" "strconv" ) type Stringer interface { String() string } type Binary uint64 func (i Binary) String() string { return strconv.Uitob64(i.Get(),2) } func (i Binary) Get() uint64 { return uint64(i) } func main() { b := Binary(200) s := Stringer(b) fmt.Println(s.String()) } Selected output of 0045 (x.go:25) LEAL s+-24(SP),BX 0046 (x.go:25) MOVL 4(BX),BP 0047 (x.go:25) MOVL BP,(SP) 0048 (x.go:25) MOVL (BX),BX 0049 (x.go:25) MOVL 20(BX),BX 0050 (x.go:25) CALL,BX The (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |