
Interface Upgrades in Go - zenazn
http://avtok.com/2014/11/05/interface-upgrades.html
======
dcposch
The zero-copy IO in Go is so elegant.

I think you can really judge a language accurately by checking out its
standard library. This is one of my favorite things about Go.

By comparison,

* The C++ STL. Fast and useful, but the implementation is nearly unreadable due to template soup. Here's one of the simpler parts! [https://www.sgi.com/tech/stl/stl_vector.h](https://www.sgi.com/tech/stl/stl_vector.h)

* PHP. So bad it's basically a strawman. I'll include it because it's hilarious: [http://www.forbes.com/sites/quora/2012/08/31/what-are-the-mo...](http://www.forbes.com/sites/quora/2012/08/31/what-are-the-most-surprisingly-useful-php-functions/)

* Java. A bit better. Compare the readability of OpenJDK's ArrayList.java to the STL vector.h, which does essentially the same thing: [http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/s...](http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/src/share/classes/java/util/ArrayList.java)

But of course the Java standard library is immense and has a lot of cruft in
it. To write clean Java you really need to avoid much of the standard library
(read Effective Java by Josh Bloch) and add a few important missing parts (use
Guava).

Golang is really unique in that regard. You can _learn Go_ by reading the
standard library. It is beautiful, linear code. The library is pretty complete
--eg you can write a one-line HTTP file server out of the box--but nothing
feels extraneous. Lastly, I think it gets close to Knuth's ideal of Literate
Programming. Paragraph-length comments thoughout the standard library explain
what's happening, how, and why.

For example, the post talks about how io.Copy is awesome. For a concise
English explanation, why not go directly to the source!

[https://golang.org/src/pkg/io/io.go#L329](https://golang.org/src/pkg/io/io.go#L329)

~~~
thomasahle
For all the comments Go gets on its typesystem, I suppose

    
    
        	if rt, ok := dst.(ReaderFrom); ok {
        		return rt.ReadFrom(src)
        	}
    

Isn't the most terrible way anyone has seen pattern matching implemented.

At least it is better than Java's instanceof + cast.

~~~
zaphar
For even more similar look to pattern matching look at Go's type switch.

    
    
        switch i.(type) {
            case OptionalInterface1:
               // do some extra thing we allow
            case OptionalInterface2:
               // do some other extra thing we allow
        }
    

It's really good when you have a set of mutually exclusive interfaces or
types.

~~~
jerf
You can do a passable impression of sum types that way too:
[http://www.jerf.org/iri/post/2917](http://www.jerf.org/iri/post/2917)

------
andolanra
The important thing is to _make sure that your 'upgrade' is semantically
indistinguishable from the non-upgraded version_, and to the extent that it
differs, _document it_. There's an old blog post[^1] where someone describes
code that performed this kind of "interface upgrade" in a way that broke
underlying assumptions about the code. This is a horrible (and unexpected!)
kind of bug. One should be really, _really_ sure that this is the correct way
of solving a problem before actually reaching for this tool.

[^1]: In particular, a function which took a Reader would attempt to cast that
Reader to something with a Close method, which broke the programmer's
assumptions about that function. It didn't help that said behavior was at the
time undocumentated: [http://how-bazaar.blogspot.co.nz/2013/07/stunned-by-
go.html](http://how-bazaar.blogspot.co.nz/2013/07/stunned-by-go.html) and the
HN discussion
[https://news.ycombinator.com/item?id=6060351](https://news.ycombinator.com/item?id=6060351)

------
TeeWEE
Note that while interface upgrades are very usefull to use, they are not
something very new in the programming language world.

In java you could do:

    
    
      if (reader instanceOf BufferedReader) {
        // upgrade
        BufferedReader bReader = (BufferedReader) reader;
      }
    

This is actualy adviced NOT to do, since its really a runtime type check.

In Go its no different, as this article explains. However its still a usefull
mechanism sometimes.

------
skybrian
I'm wondering what programmers in functional programmers do about this sort of
thing? It seems like they have much stronger type constrants; subclassing and
upcasting just plain don't work. And if they did work, it would invalidate the
sort of proofs that functional programmers like to do.

~~~
pcwalton
Treating "upgrades" as a specific case of reflection, typically what happens
is that the function that wants to do reflection declares that it wants to
perform reflection on its arguments via the type system. For example, in
Haskell this mechanism is called "typeable" and in Rust this is called "any".

The nice thing about this approach is that it signals to callers that
reflection is going to happen, so callers can be ready for it—the types become
a form of documentation. If reflection is unrestricted it's easy for callers
to get surprised when methods are called that they didn't expect. (Go has had
breakage during point release upgrades from this, for example.)

This stricter approach requires that you plan ahead for "upgrading" in advance
since you can't change a non-upgradeable argument to an upgradeable one
without changing the type signature and breaking your callers. On the other
hand, your callers are secure in the knowledge that you won't do that: they
know that if you didn't give them access to one of your type's methods they
can't sneak around and get access to it through the "back door". Non-
upgradeable interfaces can also be more efficient since there is no need to
store the reflection metadata at runtime; with this approach you only pay for
what you use. Altogether it's a classic static-versus-dynamic-typing tradeoff.

------
taliesinb
Has anyone quantified the runtime cost of doing interface upgrades? I have a
vague memory that the vtable for a specific type/interface pair is constructed
at runtime and then cached -- is this true?

~~~
jerf
An interesting question. Using [1], which note can't be executed on the
Playground but you can execute by copying and pasting into a file
"upgrade_test.go" in a new directory and executing

    
    
        GOPATH=`pwd` go test -bench=.
    

I get:

    
    
        BenchmarkInterfaceUpgrade        24.9 ns/op
        BenchmarkDoNothingInterfaceCall   5.32 ns/op
        BenchmarkInterfaceUpgradeAndCall 29.4 ns/op
        BenchmarkInterfaceUpgradeFail    23.9 ns/op
    

For clarity, I've elided the "execution count" column which is not useful
information for us here.

InterfaceUpgrade is testing the cost of having an interface-referenced value
in hand and asking if it implements another interface. My belief based on the
understanding of the runtime is that this is all a static check at runtime
(i.e., taking the type of the value, looking that type's info up, and then
reading from that type whether it implements a given interface, with all
calculation done at compile time and only the lookup being done dynamically),
and that while that number may go up a bit if there are more interfaces in
play it shouldn't go up much.

DoNothingInterfaceCall is the time to call a function that does nothing and
returns nothing through the interface. I tried adding a benchmark time for a
do-nothing static call but Go inlines it away into nothing, making it a
useless test of how fast the loop itself is running. 0.89 ns, FWIW.

InterfaceUpgradeAndCall combines the upgrade check and call into one pass.
InterfaceUpgradeFail just checks to see if the fail case times very
differently (no).

It's fairly easy to tweak these if you'd like to ask different questions.
(Note some of the tests have a bit of spurious stuff at the end to make it so
Go doesn't complain about unused values. Go really doesn't like to see unused
values.)

Also, for those who may have spent some time benchmarking the more dynamic
languages, I call your attention to the "ns" there. That's not milli- or
microseconds, that's nanoseconds. (I once benchmarked something between Go and
Erlang, and mistakingly though the Go was slower when it took 700 and Erlang
was taking 70, but it turned out to be nanoseconds and microseconds
respectively. FWIW, the code wasn't doing exactly the same thing, either, it
was just some code I was benchmarking at the time, and it is expected that the
Go code was doing less; point being, watch your units.)

[http://play.golang.org/p/K1LedMxMPL](http://play.golang.org/p/K1LedMxMPL)

------
scottpiper
The author of that article is also the author of the Goji framework (very nice
web framework for Golang): [https://goji.io/](https://goji.io/)

~~~
Sphax
I was looking for the example proxy around http.ResponseWriter the author
talked about which is implemented in Goji, I believe it's this code:
[https://github.com/zenazn/goji/blob/master/graceful/middlewa...](https://github.com/zenazn/goji/blob/master/graceful/middleware.go)

I understand it fine except this bit right at the end:

    
    
        var _ http.CloseNotifier = &fancyWriter{}
        var _ http.Flusher = &fancyWriter{}
        var _ http.Hijacker = &fancyWriter{}
        var _ io.ReaderFrom = &fancyWriter{}
    

What's the purpose of this ? According to effective go this just silences the
unused imports but I don't think there's a need for that here.

~~~
rakoo
The relevant part of Effective Go is here:
[http://golang.org/doc/effective_go.html#blank_implements](http://golang.org/doc/effective_go.html#blank_implements)

It allows you to make sure fancyWriter implements all those interfaces, so
that any future refactoring that breaks compatibility breaks at compile time.

You know how Go makes interfaces concordance implicit ? Whatever has a Read()
method is a io.Reader without asking for it ? This explicitly tells the
compiler (and other developers) the same thing.

~~~
Sphax
Thank you, I read the wrong part of effective Go apparently, didn't see this.
It's clear now.

------
Goopplesoft
Go's interfaces are great, but one of my annoyances is that you can't define
an interface with values (methods only). Which makes little sense considering
it works just fine with getters and setters. i.e:

    
    
        type Blah interface { Var string }
    

wont work, but

    
    
        type Blah interface { GetVar() string } 
    

does. I'm sure theres a reason for this. Can someone shed some light?

~~~
kjksf
The purpose of the interface is to abstract how something is done.

In your example, GetVar() can be implemented in a number of ways. Returning
Var field of Blah struct is only one such way (and if there was only one
implementation, there would be no need for an interface).

In that light, adding variables as part of interface doesn't make sense.
Variables are fixing the thing that interface is meant to make flexible.

In Go, you can achieve re-using of a bunch of variables (and their methods) by
embedding - put the variables you want to re-use into a separate struct and
embed that struct in other structs. See
[https://golang.org/doc/effective_go.html#embedding](https://golang.org/doc/effective_go.html#embedding)

~~~
Goopplesoft
It seemed to me that interfaces have a second function of providing partial
uniformity across types (not just abstracting procedures). I.e the Animal
interface has a method `Speak() string` and you have a function that takes an
Animal that prints something like "Animal type {species} says {speak}" you
would have to define a getter on the Animal's species even though its not a
really a procedure. But I see what you mean though. Thanks!

~~~
psherman
If you have a function that requires both the species and a speak message from
an animal, it would make sense for the interface to have a function that
returns both the species and speak strings. I think that this [0] does what
you would want.

If you want to guarantee that two structs contain a set of the same required
fields, I believe the best practice would be to include an anonymous field for
a struct with the fields across both. [1]

[0] [http://play.golang.org/p/0zMqPUicgq](http://play.golang.org/p/0zMqPUicgq)
[1] [http://play.golang.org/p/cM3XPfitxH](http://play.golang.org/p/cM3XPfitxH)

~~~
Goopplesoft
That second one is damn interesting, didn't know you could do that. Thanks!

------
shinamee
Is it me or the text is really hard to read?

~~~
jagger27
The line height is a little tight. The column width and typeface are fine.

