163

I'm familiar with the fact that, in Go, interfaces define functionality, rather than data. You put a set of methods into an interface, but you are unable to specify any fields that would be required on anything that implements that interface.

For example:

// Interface
type Giver interface {
    Give() int64
}

// One implementation
type FiveGiver struct {}

func (fg *FiveGiver) Give() int64 {
    return 5
}

// Another implementation
type VarGiver struct {
    number int64
}

func (vg *VarGiver) Give() int64 {
    return vg.number
}

Now we can use the interface and its implementations:

// A function that uses the interface
func GetSomething(aGiver Giver) {
    fmt.Println("The Giver gives: ", aGiver.Give())
}

// Bring it all together
func main() {
    fg := &FiveGiver{}
    vg := &VarGiver{3}
    GetSomething(fg)
    GetSomething(vg)
}

/*
Resulting output:
5
3
*/

Now, what you can't do is something like this:

type Person interface {
    Name string
    Age int64
}

type Bob struct implements Person { // Not Go syntax!
    ...
}

func PrintName(aPerson Person) {
    fmt.Println("Person's name is: ", aPerson.Name)
}

func main() {
    b := &Bob{"Bob", 23}
    PrintName(b)
}

However, after playing around with interfaces and embedded structs, I've discovered a way to do this, after a fashion:

type PersonProvider interface {
    GetPerson() *Person
}

type Person struct {
    Name string
    Age  int64
}

func (p *Person) GetPerson() *Person {
    return p
}

type Bob struct {
    FavoriteNumber int64
    Person
}

Because of the embedded struct, Bob has everything Person has. It also implements the PersonProvider interface, so we can pass Bob into functions that are designed to use that interface.

func DoBirthday(pp PersonProvider) {
    pers := pp.GetPerson()
    pers.Age += 1
}

func SayHi(pp PersonProvider) {
    fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}

func main() {
    b := &Bob{
        5,
        Person{"Bob", 23},
    }
    DoBirthday(b)
    SayHi(b)
    fmt.Printf("You're %v years old now!", b.Age)
}

Here is a Go Playground that demonstrates the above code.

Using this method, I can make an interface that defines data rather than behavior, and which can be implemented by any struct just by embedding that data. You can define functions that explicitly interact with that embedded data and are unaware of the nature of the outer struct. And everything is checked at compile time! (The only way you could mess up, that I can see, would be embedding the interface PersonProvider in Bob, rather than a concrete Person. It would compile and fail at runtime.)

Now, here's my question: is this a neat trick, or should I be doing it differently?

Simon Fox
  • 5,995
  • 1
  • 18
  • 22
Matt Mc
  • 8,882
  • 6
  • 53
  • 89
  • 8
    "I can make an interface that defines data rather than behavior". I would argue that you have a behaviour that returns data. – jmaloney Sep 24 '14 at 22:22
  • I'm gonna write an answer; I think it's fine if you need it and know the consequences, but there are consequences and I wouldn't do it all the time. – twotwotwo Sep 24 '14 at 22:22
  • @jmaloney I think you're right, if you wanted to look at it plainly. But overall, with the different pieces I've shown, the semantics become "this function accepts any struct that has a ___ in its composition". At least, that's what I intended. – Matt Mc Sep 25 '14 at 00:11
  • 1
    This isn't "answer" material. I got to your question by googling "interface as struct property golang". I found a similar approach by setting a struct that implements an interface as the property of another struct. Here's the playground, https://play.golang.org/p/KLzREXk9xo Thanks for giving me some ideas. – Dale Aug 01 '16 at 04:59
  • Isn't the embedded struct just composition? – Alexander Mills Dec 03 '18 at 00:32
  • The whole idea works even without embedded structs. The above example [here](https://play.golang.org/p/rJY8RD-z7HM) without the "Bob struct". The key idea to all this is the return value of the method of the interface. If it wasn't a pointer to the "Person" struct, it [wouldn't have worked](https://play.golang.org/p/G-wJCrN1JWi). – mchar Apr 03 '19 at 14:29
  • My question is: why cannot you have implicit data interface implementations like you can have implicit method interface implementations? – pihentagy Apr 07 '19 at 20:32
  • 7
    In retrospect, and after 5 years of using Go, it's clear to me that the above is not idiomatic Go. It's a straining towards generics. If you feel tempted to do this sort of thing, I advise you to rethink the architecture of your system. Accept interfaces and return structs, share by communicating, and rejoice. – Matt Mc Apr 08 '19 at 06:22
  • Why use embedded struct? It works without it. Anyways, IMO, this is not a good way to use interface. This feels like... *not using an interface by using an interface.* ;-) – starriet Feb 28 '22 at 11:44

2 Answers2

66

It is definitely a neat trick. However, exposing pointers still makes direct access to data available, so it only buys you limited additional flexibility for future changes. Also, Go conventions do not require you to always put an abstraction in front of your data attributes.

Taking those things together, I would tend towards one extreme or the other for a given use case: either a) just make a public attribute (using embedding if applicable) and pass concrete types around or b) if exposing the data seems to complicate some implementation change you think is likely, expose it through methods. You're going to be weighing this on a per-attribute basis.

If you're on the fence, and the interface is only used within your project, maybe lean towards exposing a bare attribute: if it causes you trouble later, refactoring tools can help you find all the references to it to change to a getter/setter.


Hiding properties behind getters and setters gives you some extra flexibility to make backwards-compatible changes later. Say you someday want to change Person to store not just a single "name" field but first/middle/last/prefix; if you have methods Name() string and SetName(string), you can keep existing users of the Person interface happy while adding new finer-grained methods. Or you might want to be able to mark a database-backed object as "dirty" when it has unsaved changes; you can do that when data updates all go through SetFoo() methods. (You could do it other ways, too, like stashing the original data somewhere and comparing when a Save() method is called.)

So: with getters/setters, you can change struct fields while maintaining a compatible API, and add logic around property get/sets since no one can just do p.Name = "bob" without going through your code.

That flexibility is more relevant when the type is complicated (and the codebase is big). If you have a PersonCollection, it might be internally backed by an sql.Rows, a []*Person, a []uint of database IDs, or whatever. Using the right interface, you can save callers from caring which it is, the way io.Reader makes network connections and files look alike.

One specific thing: interfaces in Go have the peculiar property that you can implement one without importing the package that defines it; that can help you avoid cyclic imports. If your interface returns a *Person, instead of just strings or whatever, all PersonProviders have to import the package where Person is defined. That may be fine or even inevitable; it's just a consequence to know about.


But again, the Go community does not have a strong convention against exposing data members in your type's public API. It's left to your judgment whether it's reasonable to use public access to an attribute as part of your API in a given case, rather than discouraging any exposure because it could possibly complicate or prevent an implementation change later.

So, for example, the stdlib does things like let you initialize an http.Server with your config and promises that a zero bytes.Buffer is usable. It's fine to do your own stuff like that, and, indeed, I don't think you should abstract things away preemptively if the more concrete, data-exposing version seems likely to work. It's just about being aware of the tradeoffs.

twotwotwo
  • 28,310
  • 8
  • 69
  • 56
  • One additional thing: the embedding approach is a bit more like inheritance, right? You get any fields and methods that the embedded struct has, and you can use its interface so that any superstruct will qualify, without re-implementing sets of interfaces. – Matt Mc Sep 26 '14 at 02:22
  • Yeah--a lot like virtual inheritance in other langs. You can use embedding to implement an interface whether it's defined in terms of getters and setters or a pointer to the data (or, a third option for readonly access to tiny structs, a copy of the struct). – twotwotwo Sep 26 '14 at 04:14
  • I have to say, this is giving me flashbacks to 1999 and learning to write reams of boilerplate getters and setters in Java. – Tom Feb 04 '20 at 15:24
  • It's too bad Go's own standard library doesn't always do this. I'm in the middle of trying to mock out some calls to os.Process for unit tests. I can't just wrap the process object in an interface since the Pid member variable is accessed directly and Go interfaces don't support member variables. – Alex Jansen Feb 22 '20 at 03:57
  • 1
    @Tom That's true. I do think getters/setters add more flexibility than exposing a pointer, but I _also_ don't think everyone should getter/setter-ify everything (or that that would match typical Go style). I previously had a few words gesturing at that, but revised the beginning and end to emphasize it a lot more. – twotwotwo Feb 22 '20 at 06:35
  • Getters and Setters are not welcomed patterns in Go. – Igor A. Melekhine Sep 22 '20 at 07:29
10

If I correctly understand you want to populate one struct fields into another one. My opinion not to use interfaces to extend. You can easily do it by the next approach.

package main

import (
    "fmt"
)

type Person struct {
    Name        string
    Age         int
    Citizenship string
}

type Bob struct {
    SSN string
    Person
}

func main() {
    bob := &Bob{}

    bob.Name = "Bob"
    bob.Age = 15
    bob.Citizenship = "US"

    bob.SSN = "BobSecret"

    fmt.Printf("%+v", bob)
}

https://play.golang.org/p/aBJ5fq3uXtt

Note Person in Bob declaration. This will be made the included struct field be available in Bob structure directly with some syntactic sugar.

fatal_error
  • 5,457
  • 2
  • 18
  • 18
  • 1
    Doing something like this will fail: `bob := &Bob{Name: "Bob"}` – Bijan Sep 19 '20 at 08:47
  • Bijan is correct, why can you only make assignments to the above using dot notation? – NomNomCameron Sep 21 '20 at 02:08
  • 6
    This is the syntactical sugar of the "struct embedding" mechanism, if you want to make an assignment on the allocation point you need to use more explicit declaration: `bob := &Bob{Person: Person{Name: "Bob"}}` – Igor A. Melekhine Sep 21 '20 at 11:11