Home > Back-end >  Using golang nesting structs (with interfaces)
Using golang nesting structs (with interfaces)

Time:10-16

In two words, it's not a secret for anyone that for a good architecture we should use interfaces, those describe it's behavior. Golang implemented this idea, but their interfaces has only methods, but not fields. So, the only way to use them is like creating getters and setters. But with that I've got a problem with pointers.

For example:


package main

import "fmt"

// A

type IA interface {
    Foo() string
}

type A struct {
    foo string
}

func (a *A) Foo() string {
    return a.foo
}

// B

type IB interface {
    A() *IA
}

type B struct {
    a *IA
}

func (b *B) A() *IA {
    return b.a
}

// main

func main() {
    a := &A{"lol"}
    b := &B{a} // cannot use a (type *A) as type *IA in field value: *IA is pointer to interface, not interface

    foo := b.A().Foo() // b.A().Foo undefined (type *IA is pointer to interface, not interface)
    fmt.Println(foo)
}


Ofc, I can use something like this:

(*(*b).A()).Foo()

but will this be that good and appropriate?

I just wanna have behavior like in python, js, ts:

someObj.child1.child2.child2SomeMethod()

Maybe I messed up with pointers, I just wanna know the golang way to work with nested objects.

CodePudding user response:

This is a common point to get tripped up, especially for folks new to go with backgrounds in higher level languages. Here's how I keep it straight:

First of all, let's identify what a "receiver" (eg "method") is in Go. Much like python, the fact that a method is actually connected to a type is syntactic sugar. Consder this example:

package main

import "fmt"

type F struct {
  i int
}

func (f F)Foo() {
  fmt.Println(f.i)
}

func main() {
  F.Foo(F{1})
}

Surprising as it may be, this code compiles and successuflly prints out the expected 1. That's because what really happens when you call a receiver on a type, is that the type becomes the first argument of the receiver.

Really quick let's review pointers too, as you seem to have said it backwards in the comments. So just to be clear: any value in a computer program is stored in memory, and it's address in memory is also a value that can be stored in a variable. We call these variables "pointers to" the value.

If I pass a function a value, they can only change that value within the scope of their function. If that value is an address in memory, the same thing is true. but, I can change the data at that address, thus affecting data that has a scope outside the function.

package main
import "fmt"

func f(i int) { i = i   2 }

func pf(i *int) { *i = *i   2 }

var i = 1

func main() {
  f(i)
  fmt.Println(i)
  pf(&i)
  fmt.Println(i)
}

Prints out

1
3

f(i) changes its local copy of i , but pf(&i) changes the data at the address stored in i.

Why did I go through all that? Because that's the reason most receivers in go are pointer receivers; because you don't want to pass a copy of the receiver; you actally want to pass the address of the receiver so that it can mutate itself within its own method.

Keep in mind receivers are syntactic sugar:

func (t *Type)f(arg string)

is equivalent to:

func f(t *Type, arg string)

Hopefully that makes it clear why pointer receivers are so common! Okay, on to the interface side of things.

An interface, as you know, defines a set of methods. A type satisfies that interface if it defines methods with the same signatures. This can be true of value receivers or pointer receivers.

However, a type cannot have both value and pointer receivers with the same name:

func (t  T)F() {}
func (t *T)F() {} //method redeclared: T.F

So that means that a type and a pointer to the type cannot have the same receiver; thus either the type or a pointer to the type implements the receiver, but not both. This point is easy to overlook because go will automatically convert. So this works fine:


type T struct{}

func (t  T)F() {}
func (t *T)PF() {}

func main() {
 var t T 
 t.F()
 t.PF()
}

in t.PF(), t is automatically converted to a pointer.

But to reiterate, a type and a pointer to type cannot both define the same receiver. So if T satisfies interface I, *T does not, and vise versa.

That having been said and understood, it's easy to come up with an easy rule: never take a pointer to an interface when you mean to satisfy an interface with a pointer.

In your code, *A satisfies IA. So you could say your IA is "really an *A. Thinking of it like this, you can see that taking the address of an IA that is actually an *A` doesn't make any sense.

Putting it all together, here's the definition two interfaces and chaining the call. Note that while pointers to my structs may be the value satisfying the interface, I never need to take the address of an interface. To the consumer of IF and IG interfaces, whether they are types or pointer receivers are not relevant.

package main

import "fmt"

type IF interface {
  G() IG
}

type F struct {
  g IG
}

func (f *F)G() IG {
  return f.g
}

type IG interface {
  G()
}

type G struct{
  i int
}

func (g *G)G() {
  g.i  
  fmt.Println("Gee, ", g.i)
}

func main() {
  f := F{&G{1}}
  f.G().G()
}

It's very uncommon to need a pointer to an interface, so make sure you think "interface satisfied by pointer" and not "pointer to interface".

  • Related