2

I'm a bit confused when I see code such as:

bigBox := &BigBox{}
bigBox.BubbleGumsCount = 4          // correct...
bigBox.SmallBox.AnyMagicItem = true // also correct

Why, or when, would I want to do bigBox := &BigBox{} instead of bigBox := BigBox{} ? Is it more efficient in some way?

Code sample was taken from here.

Sample no.2:

package main

import "fmt"

type Ints struct {
  x int
  y int
}

func build_struct() Ints {
  return Ints{0,0}
}

func build_pstruct() *Ints {
  return &Ints{0,0}
}

func main() {
  fmt.Println(build_struct())
  fmt.Println(build_pstruct())
}

Sample no. 3: ( why would I go with &BigBox in this example, and not with BigBox as a struct directly ? )

func main() {
  bigBox := &BigBox{}
  bigBox.BubbleGumsCount = 4 
  fmt.Println(bigBox.BubbleGumsCount)
}

Is there ever a reason to call build_pstruct instead of the the build_struct variant? Isn't that why we have the GC?

Geo
  • 93,257
  • 117
  • 344
  • 520

5 Answers5

5

I figured out one motivation for this kind of code: avoidance of "struct copying by accident".


If you use a struct variable to hold the newly created struct:

bigBox := BigBox{}

you may copy the struct by accident like this

myBox := bigBox // Where you just want a refence of bigBox.
myBox.BubbleGumsCount = 4

or like this

changeBoxColorToRed(bigBox)

where changeBoxColorToRed is

// It makes a copy of entire struct as parameter. 
func changeBoxColorToRed(box bigBox){
    // !!!! This function is buggy. It won't work as expected !!!
    // Please see the fix at the end.
    box.Color=red
}

But if you use a struct pointer:

bigBox := &BigBox{}

there will be no copying in

myBox := bigBox

and

changeBoxColorToRed(bigBox)

will fail to compile, giving you a chance to rethink the design of changeBoxColorToRed. The fix is obvious:

func changeBoxColorToRed(box *bigBox){
    box.Color=red
}

The new version of changeBoxColorToRed does not copy the entire struct and works correctly.

Kevin Yuan
  • 1,008
  • 8
  • 20
  • Wow, this actually makes sense! – Geo Sep 25 '13 at 10:50
  • You function changeBoxColorToRed is misleading since it won't change the color – ClojureMostly Sep 25 '13 at 21:37
  • 3
    The same is true for return arguments. For example to answer sample 2 in the question, see http://play.golang.org/p/MFZUqXILr6 for an example. Notice the struct return, there are actually two structs allocated. The pointer return points to the same one that was allocated in the function... – J. Holmes Sep 26 '13 at 03:55
  • If you understand what a pointer is, you won't "copy a struct by accident". Someone with a C/C++ background will know this, or any language that has pointers. Perhaps not PHP, as PHP objects are always passed by reference (internally) since PHP 5. – Luke Sep 30 '13 at 16:56
3

bb := &BigBox{} creates a struct, but sets the variable to be a pointer to it. It's the same as bb := new(BigBox). On the other hand, bb := BigBox{} makes bb a variable of type BigBox directly. If you want a pointer (because perhaps because you're going to use the data via a pointer), then it's better to make bb a pointer, otherwise you're going to be writing &bb a lot. If you're going to use the data as a struct directly, then you want bb to be a struct, otherwise you're going to be dereferencing with *bb.

It's off the point of the question, but it's usually better to create data in one go, rather than incrementally by creating the object and subsequently updating it.

bb := &BigBox{
    BubbleGumsCount: 4,
    SmallBox: {
        AnyMagicItem: true,
    },
}
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • So in the end it's just a matter of personal preference, between using the struct as a struct, or as a pointer? – Geo Sep 22 '13 at 09:39
  • No, it's not personal preference: values and pointers have different semantics and quite different performance implications. – Paul Hankin Sep 22 '13 at 09:43
  • Yes, I understand that. But if in my entire program, I only have this inside main: `bb := &BigBox{BubbleGumsCount:4}; fmt.Println(bb.BubbleGumsCount);`, why would I want to declare BigBox to be a pointer instead of a regular struct? – Geo Sep 22 '13 at 09:48
2

The & takes an address of something. So it means "I want a pointer to" rather than "I want an instance of". The size of a variable containing a value depends on the size of the value, which could be large or small. The size of a variable containing a pointer is 8 bytes.

Here are examples and their meanings:

bigBox0 := &BigBox{} // bigBox0 is a pointer to an instance of BigBox{}
bigBox1 := BigBox{} // bigBox1 contains an instance of BigBox{}
bigBox2 := bigBox // bigBox2 is a copy of bigBox
bigBox3 := &bigBox // bigBox3 is a pointer to bigBox
bigBox4 := *bigBox3 // bigBox4 is a copy of bigBox, dereferenced from bigBox3 (a pointer)

Why would you want a pointer?

  1. To prevent copying a large object when passing it as an argument to a function.
  2. You want to modify the value by passing it as an argument.
  3. To keep a slice, backed by an array, small. [10]BigBox would take up "the size of BigBox" * 10 bytes. [10]*BigBox would take up 8 bytes * 10. A slice when resized has to create a larger array when it reaches its capacity. This means the memory of the old array has to be copied to the new array.

Why do you not what to use a pointer?

  1. If an object is small, it's better just to make a copy. Especially if it's <= 8 bytes.
  2. Using pointers can create garbage. This garbage has to be collected by the garbage collector. The garbage collector is a mark-and-sweep stop-the-world implementation. This means that it has to freeze your application to collect the garbage. The more garbage it has to collect, the longer that pause is. This individual, for example. experienced a pause up to 10 seconds.
  3. Copying an object uses the stack rather than the heap. The stack is usually always faster than the heap. You really don't have to think about stack vs heap in Go as it decides what should go where, but you shouldn't ignore it either. It really depends on the compiler implementation, but pointers can result in memory going on the heap, resulting in the need for garbage collection.
  4. Direct memory access is faster. If you have a slice []BigBox and it doesn't change size it can be faster to access. []BigBox is faster to read, whereas []*BigBox is faster to resize.

My general advice is use pointers sparingly. Unless you're dealing with a very large object that needs to be passed around, it's often better to pass around a copy on the stack. Reducing garbage is a big deal. The garbage collector will get better, but you're better off by keeping it as low as possible.

As always test your application and profile it.

Luke
  • 13,678
  • 7
  • 45
  • 79
1

The difference is between creating a reference object (with the ampersand) vs. a value object (without the ampersand).

There's a nice explanation of the general concept of value vs. reference type passing here... What's the difference between passing by reference vs. passing by value?

There is some discussion of these concepts with regards to Go here... http://www.goinggo.net/2013/07/understanding-pointers-and-memory.html

Community
  • 1
  • 1
Carter
  • 2,850
  • 9
  • 44
  • 57
  • Ok, but why would we want to create reference objects in the above case? I've read over the last article, and it's still not clear for me. – Geo Sep 21 '13 at 22:46
  • No idea for that specific example. Perhaps pose the question to the author in their comment section. – Carter Sep 21 '13 at 22:59
1

In general there is no difference between a &BigBox{} and BigBox{}. The Go compiler is free to do whatever it likes as long as the semantics are correct.

func StructToStruct() {
    s := Foo{}
    StructFunction(&s)
}

func PointerToStruct() {
    p := &Foo{}
    StructFunction(p)
}

func StructToPointer() {
    s := Foo{}
    PointerFunction(&s)
}

func PointerToPointer() {
    p := &Foo{}
    PointerFunction(p)
}

//passed as a pointer, but used as struct
func StructFunction(f *Foo) {
    fmt.Println(*f)
}

func PointerFunction(f *Foo) {
    fmt.Println(f)
}

Summary of the assembly:

  • StructToStruct: 13 lines, no allocation
  • PointerToStruct: 16 lines, no allocation
  • StructToPointer: 20 lines, heap allocated
  • PointerToPointer: 12 lines, heap allocated

With a perfect compiler the *ToStruct functions would be the identical as would the *ToPointer functions. Go's escape analysis is good enough to tell if a pointer escapes even across module boundries. Which ever way is most efficient is the way the compiler will do it.

If you're really into micro-optimization note that Go is most efficient when the syntax lines up with the semantics (struct used as a struct, pointer used as a pointer). Or you can just forget about it and declare the variable the way it will be used and you will be right most of the time.

Note: if Foo is really big PointerToStruct will heap allocate it. The spec threatens to that even StructToStruct is allowed to do this but I couldn't make it happen. The lesson here is that the compiler will do whatever it wants. Just as the details of the registers is shielded from the code, so is the state of the heap/stack. Don't change your code because you think you know how the compiler is going to use the heap.

deft_code
  • 57,255
  • 29
  • 141
  • 224
  • No! This is absolutely incorrect. You cannot make assumptions of stack/heap in GO. Go is not C and it is not specified. See Rob Pike's answer: https://groups.google.com/d/msg/golang-nuts/PH_pMZqvLN8/xd12p5x5qqUJ and the FAQ: http://golang.org/doc/faq?ModPagespeed=noscript#stack_or_heap Please edit your answer – ClojureMostly Sep 25 '13 at 21:42