in ,

Go Interfaces, the Tricky Parts, Hacker News


                

          

Does this Go snippet compile, and if not, why?

func main () {   users:=[] User {User {"alice"}, User {"bob"}}    // hint: this line compiles, User fulfils Named   var _ Named=User {"charlie"}    getName (users [0])   getNames (users) }  func getName (n Named) string {n.name ()} func getNames (ns [] Named) [] string {/ * ... * /}

It doesn’t compile. Surprisingly, althoughUserimplements (Named) , and thegetNamecall is fine, thegetNames (users)call is a type error.

When I first came to Go it confused me that ifUserimplementedNamedI wasn’t able to use aUserwherever I saw aNamed) in the types of arguments. Instead, although I could passUserto a method acceptingNamed:

  • I couldn’t pass[] Useras a[] Named
  • I couldn’t pass[] Useras an[] interface {}
  • I couldn’t pass* Useras* Named
Here’s a fuller example demonstrating all **********************package main   import "fmt"   func main () {     users:=[] User {User {"alice"}, User {"bob"}}     getName (users [0])        // cannot use users (type [] User) as type [] Named in     // argument to getNames     getNames (users)     // cannot use users (type [] User) as type [] interface     // {} in argument to countSlice     countSlice (users)     // cannot (type * User) as type * Named in argument     // to switchNamed:     switchNamed (& users [0], & users [1])   }   func getName (n Named) string {      return n.name ()   }   func getNames (ns [] Named) [] string {/ * ... * /}   func countSlice (ns [] interface {}) int {     return len (ns)   }   func switchNamed (a, b * Named) {/ * ... * /}   type User struct {     fullName string   }   func (u User) name () string {return u.fullName}   type Named interface {     name () string   }     

Runnable code

Why can’t Go be like$ language?

It felt inconsistent. It was also very different from languages ​​like TypeScript and Java, where ifUserimplementedNamed, as well as being able to passUserto a method acceptingNamed:

  • I could passUser []as aNamed []
  • I could passUser []as anany [](TypeScript only)

For instance, here’s the same example in TypeScript (minusswitchNamedas TS doesn’t have pointers) which compiles and runs without a hitch:

function main () {   const users=[new User("alice"), new User("bob")]    console.log (       getName (new User ("charlie")),       getNames (users),       countArray (users),   ) }  class User {   constructor (private fullName: string) {}   name () {return this.fullName} }  interface Named {   name (): string }  function getName (n: Named) {return n.name ()} function getNames (ns: Named []) {return ns.map (n=>n.name ())} function countArray (ns: any []) {return ns.length}

The mystery I wanted to solve was why Go worked exactly the same way in some places (getName (users [0])), but not in others (getNamesandcountSlice). To solve it I had to learn how Go’s interfaces really worked.

Clearing up the mystery

First, an intuition for the difference between interface values ​​and concrete values ​​in Go. Imaging helping your friend move their pets to a new home. They give you two boxes, one labeled ‘friendly snakes’, one ‘fluffy spiders’. Easy enough, sling both boxes in your car and drive! But does that scenario prove you could do the same if they just handed you the critters unboxed? Not at all.

Equivalently, do not assume that because you can store a concreteUserinaNamedinterface value, you can use that concrete valueasan interface value. A concrete value and an interface value containing it are fundamentally different things and can be used in different ways.

But if that’s true, how islogOneable to acceptUservalues? Simple: the Go compiler automatically creates interface values ​​for you where it can. The two lines below are equivalent:

// Go implicitly allocates an interface value and places the User in it logOne (User {"charlie"}) // ... equivalent to us manually allocating one and passing it var f1 Named=User {"charlie"} logOne (f1)

What interface values ​​are really

Let’s make it concrete by considering what these ‘boxes’ look like. Go’s interface values ​​are really apair of pointers. When you put a concrete value into an interface value, one pointer starts pointing at the value. The second will now point to the implementation of the interface for the type of the concrete value. They’re called the dynamic value and the dynamic type respectively – dynamic because both are set at runtime when we assign concrete values ​​into an interface value.

Go interface value

This pair of pointers is the secret to how Go’s interfaces work. When a method is called on an interface value, Go follows the implementation pointer to find the appropriate method and the value pointer to be able to use the value as the receiver (or it panics if the ‘box’ is empty: a nil value) . Thus you can see why code that works with an interface value really cannot work on concrete values ​​alone.

The slice inconsistency

A check on your understanding: why can’t we pass a[] Useras a[] Named? Try to think through what the two slices / backing arrays would look like in memory.

Answer

The crux is again that an interface value is a different thing to a concrete value, rather than – as in other languages ​​- just another way a concrete value can be used. Each cell in an interface value will contain one of those familiar double pointer values:

     

Go interface slice vs value slice

However, given Go can automatically allocate aNamedwhere required, couldn’t Go also allocate a[] Namedautomatically to let you pass a(User) togetNames? It's certainly possible to imagine how Go could do it. It could allocate a new slice of interface values ​​for you, and assign the concreteUservalues ​​into corresponding indexes:

users:=[] User {User {"alice"}, User {"bob"} } // below is what Go would have to do to make switchItems (users, 0, 1) work implicitly ns:=make ([] Named, 0, len (users)) for _, u:=range users {   var n Named=u   ns=append (ns, n) // * }  getNames (ns)

Indeed, forgetNames (users)this would actually work fine! But slices have mutable indexes. and many functions mutate the slices they’re passed. Can you see why automatically creating new slices to pass in would not work as desired for such functions? For instance:

func switchItems (xs [] Named, a, b int) {   t:=xs [a]   xs [a]=xs [b]   xs [b]=t }  users:=[] User {User {"alice"}, User {"bob"}} // not possible, but imagine it was and Go did the implicit // allocate / initialize we did above switchItems (users, 0, 1)

The implicit allocation would stopswitchItemsfrom working as we’d only be affecting the newly allocated slice / backing array –nsin the example above where I showed you what Go would have to do to make it work.

Again, Go’s helpful implicit conversions can make this confusing – forappend (ns, n)we could just doappend (ns, u), but now we know Go must be allocating the interface value for us.

Takeaways

If get a type error when you attempt to use a concrete value in a place needing an interface value and you think “but it fulfils the interface”, remember: interface values ​​and concretes are very different things. That you can place a concrete value in an interface value does not mean it is an interface value, so you can’t use it in all the same ways.

For instance, if you have a function accepting[] interface {}you can’t pass a slice of concrete values. Although we know all interfaces fulfil the empty interface, an interface value is still different from a concrete value: it’s a container which allows us to peek in and extract the concrete type via type assertions. So if we have a slice of concrete values ​​we’re going to have to allocate interface values ​​to pass in:

func findAndRemoveUsers (xs [] interface {}) {   for i, x:=range xs {     if _, ok:=x. (User); ok {       fmt.Println ("There is a user at", i)       xs [i]=nil     }   } }  users:=[] User {User {"alice"}, User {"charlie"}}  // could just as well be [] interface {} ivs:=make ([] Named, 0, len (users)) for _, u:=range users {   ivs=append (ivs, u) } findAndRemoveUsers (ivs)  // double check it's clear to you why this couldn't be // supported automatically - consider xs [i]=nil findAndRemoveUsers (users)

Quiz time

Why couldn’t Go do something clever to let us pass a pointer to aUserto a method accepting a pointer toNamed?

func switchNamed (a, b * Named) {   t:=* a   * a=* b   * b=t }  u1:=User {"denise"} // type error switchNamed (& u1)
Spoiler

Much like with the slice example above, `switchNamed (& u1)` to work Go would have to automatically allocate an interface value. If it did, and passed that into the function, assignments to that fresh* Namedwould have no affect on the original* User, and so no visibility outside the function. So instead it’s a compile-time type error:

// this compiles fine var f1 Named=User {"alice"} var f2 Named=User {"bob"} fmt.Println (f1, f2) // {"alice", "bob"} switchNamed (& f1, & f2) fmt.Println (f1, f2) // {"bob", "alice"}  u1:=User {"charlie"} u2:=User {"denise"} fmt.Println (u1, u2) // {"charlie", "denise"}  // this won't compile - 'can't use type * User as * Named' // switchNamed (& u1, & u2)  // because if we implicitly generated new * Named values // for the & u1, & u2 passed to the method ... var n1 Named=u1 var n2 Named=u2 switchNamed (& n1, & n2) // .... we'd end up with u1 and u2 unaffected! fmt.Println (u1, u2) // {"charlie", "denise"}

Note: pointers to interfaces are fairly rare in practice.

    Previous:      In browser JS to C compiler       

Enjoy this? Subscribe to myRSS feedorfollow meon Twitter.


        

Brave Browser
Read More

What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

Alberto Salazar: Mo Farah's former coach banned from athletics for four years after doping violations – BBC Sport, BBC News

Alberto Salazar: Mo Farah's former coach banned from athletics for four years after doping violations – BBC Sport, BBC News

Crohn disease treatment may get improved with AI-based methods – TheHealthSite, Thehealthsite.com

Crohn disease treatment may get improved with AI-based methods – TheHealthSite, Thehealthsite.com