4

If I have a struct type A which is used as a pointer (has pointer receivers only, the constructor returns *A, etc.), what is the difference between embedding a struct type B as B versus *B?

That is, what is the difference between

type B struct {...}
type A struct {
    B
    // ...
}

and

type B struct {...}
type A struct {
    *B
    // ...
}

For example, is there ever copying of the embedded field?

Edit: I should also mention that the embedded struct B only has pointer receivers.

2 Answers 2

6

The zero values of the two structures are different, which can be a significant ergonomic difference.

Consider an embedded type

type B struct {
    X int
}

func (b *B) Print() { fmt.Printf("%d\n", b.X) }

If we embed this directly as an object

type AObj struct {
    B
}

then the zero value of type AObj includes an embedded object of type B, which also has its zero value, and therefore we can safely

var aObj AObj
aObj.Print() // prints 0

But if we instead embed a pointer

type APtr struct {
    *B
}

the zero value of this struct has a nil pointer value, and we can't really use it directly.

var aPtr APtr
aPtr.Print() // panics

Objects get copied in hopefully the way you might expect. If you create a new AObj object, it gets a copy of the embedded B.

aObj2 := aObj
aObj.X = 1
aObj2.Print() // prints 0, because it has a copy

If you create a new APtr object, it gets a copy of the *B, which means it shares the underlying concrete object.

aPtr.B = &B{}
aPtr2 := aPtr
aPtr.X = 1
aPtr2.Print() // prints 1, because both objects point at the same B

Runnable example at https://play.golang.org/p/XmOgegwVFeE

Sign up to request clarification or add additional context in comments.

Comments

3

Consider a simple example program. A structAPtr embeds a pointer, a structAVal embeds a struct structB directly:

package main

import "fmt"

type structB struct {
    foo int
}

type structAPtr struct {
    bar *structB
}

type structAVal struct {
    bar structB
}

func main() {
    // referencing bStruct
    b1 := structB{foo: 12}

    aPtr := structAPtr{bar: &b1}
    fmt.Println("Before assignment:")
    fmt.Printf("aPtr.bar.foo = %d, b.foo = %d\n", aPtr.bar.foo, b1.foo)

    aPtr.bar.foo = 42
    fmt.Println("After assignment:")
    fmt.Printf("aPtr.bar.foo = %d, b.foo = %d\n", aPtr.bar.foo, b1.foo)

    // copying bStruct
    b2 := structB{foo: 12}

    aVal := structAVal{bar: b2}
    fmt.Println("Before assignment:")
    fmt.Printf("aVal.bar.foo = %d, b.foo = %d\n", aVal.bar.foo, b2.foo)

    aVal.bar.foo = 42
    fmt.Println("After assignment:")
    fmt.Printf("aVal.bar.foo = %d, b.foo = %d\n", aVal.bar.foo, b2.foo)
}

The int structB.foo is used to demonstrate whether structB changes when manipulated inside of structAPtr or structAVal.

This program outputs:

Before assignment:
aPtr.bar.foo = 12, b.foo = 12
After assignment:
aPtr.bar.foo = 42, b.foo = 42 <------------ both changed
Before assignment:
aVal.bar.foo = 12, b.foo = 12
After assignment:
aVal.bar.foo = 42, b.foo = 12 <------------ only assignee changed

Looking at the result shows:

  • changing the value of the pointer to structB changes structB

  • changing the value of the copied version structB in structAVal leaves structB unaffected (it is still 5, even after 42 was assigned to aVal)


Edit:

If your structB has only pointer receivers anyways, the intended behavior is probably such that changing structB in strucA updated both of them. That's scenario 1 in my example and requires a pointer for sure. From A Tour of Go:

Methods with pointer receivers can modify the value to which the receiver points [...]. Since methods often need to modify their receiver, pointer receivers are more common than value receivers.


Hope that helps!

2 Comments

Very useful example. Also working with pointers has the nil pointer panic trouble... I mean this
This not embedding !!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.