Defining golang struct function using pointer or not

Go

Go Problem Overview


Can someone explain to me why appending to an array works when you do this:

func (s *Sample) Append(name string) {
	d := &Stuff{
		name: name,
	}
	s.data = append(s.data, d)
}

Full code here

But not when you do this:

func (s Sample) Append(name string) {
	d := &Stuff{
		name: name,
	}
	s.data = append(s.data, d)
}

Is there any reason at all why you would want to use the second example.

Go Solutions


Solution 1 - Go

As mentioned in the FAQ

Should I define methods on values or pointers?
func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

> First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.)

> In the examples above, if pointerMethod modifies the fields of s, the caller will see those changes, but valueMethod is called with a copy of the caller's argument (that's the definition of passing a value), so changes it makes will be invisible to the caller.

In your case, func (s Sample) Append(name string) modifies a copy.

laher reminds us in the comments that using a value instead of pointer also means getting a copy, and respecting the immutable nature of an object::

> You'd want to use the non-pointer valueMethod when (for nstance) you're returning a [value derived from an] 'immutable' private property.

See "Why are receivers pass by value in Go?":

> Can be useful if for instance you have a small immutable object. The caller can know for certain that this method doesn't modify it's receiver.
They can't know this if the receiver is a pointer without reading the code first.

Solution 2 - Go

Go slices are a tricky beast. Internally, a variable of slice type (like []int) looks like this:

struct {
    data *int // pointer to the data area
    len  int
    cap  int
}

When you pass a slice to a function, this structure is passed by value, while the underlying data area (i.e. what data points to) is not copied. The builtin append() function modifies the data area (or generates a new one) and returns a new slice with updated len, data, and cap values. If you want to overwrite anything that is not part of the underlying data area, you need to pass a pointer to the slice or return a modified slice.

Solution 3 - Go

While most of the answers here capture exactly what happens, I wanted to dig a bit into how/why that's happening. I started with a couple small snippets of code:

  1. pointer method

     package main
    
     import "fmt"
    
     type Bar struct{}
    
     func (b *Bar) Print() {
     	fmt.Println("debosmit ray")
     }
    
     func main() {
         b := Bar{}
         b.Print()
     }
    
  2. value method

     package main
    
     import "fmt"
    
     type Bar struct{}
    
     func (b Bar) Print() {
         fmt.Println("debosmit ray")
     }
    
     func main() {
         b := Bar{}
         b.Print()
     }
    

Then, I wanted to look at the assembly for just the files (generated using go tool compile -S filename.go > filename.S, for each of the files. Both the outputs are available here (should be available forever).

Let's take a look at the output of diff pointer.S value.S (pointer -> has pointer method, value -> has value method).

14,15c14,15
< "".(*Bar).Print STEXT size=138 args=0x8 locals=0x58
< 	0x0000 00000 (bar.go:7)	TEXT	"".(*Bar).Print(SB), ABIInternal, $88-8
---
> "".Bar.Print STEXT size=138 args=0x0 locals=0x58
> 	0x0000 00000 (bar.go:7)	TEXT	"".Bar.Print(SB), ABIInternal, $88-0
24c24
< 	0x001d 00029 (bar.go:7)	FUNCDATA	$0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
---
> 	0x001d 00029 (bar.go:7)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
26c26
< 	0x001d 00029 (bar.go:7)	FUNCDATA	$3, "".(*Bar).Print.stkobj(SB)
---
> 	0x001d 00029 (bar.go:7)	FUNCDATA	$3, "".Bar.Print.stkobj(SB)
126a127,200
> "".(*Bar).Print STEXT dupok size=187 args=0x8 locals=0x58
> 	0x0000 00000 (<autogenerated>:1)	TEXT	"".(*Bar).Print(SB), DUPOK|WRAPPER|ABIInternal, $88-8
> 	0x0000 00000 (<autogenerated>:1)	MOVQ	(TLS), CX
> 	0x0009 00009 (<autogenerated>:1)	CMPQ	SP, 16(CX)
> 	0x000d 00013 (<autogenerated>:1)	PCDATA	$0, $-2
> 	0x000d 00013 (<autogenerated>:1)	JLS	154
> 	0x0013 00019 (<autogenerated>:1)	PCDATA	$0, $-1
> 	0x0013 00019 (<autogenerated>:1)	SUBQ	$88, SP
> 	0x0017 00023 (<autogenerated>:1)	MOVQ	BP, 80(SP)
> 	0x001c 00028 (<autogenerated>:1)	LEAQ	80(SP), BP
> 	0x0021 00033 (<autogenerated>:1)	MOVQ	32(CX), BX
> 	0x0025 00037 (<autogenerated>:1)	TESTQ	BX, BX
> 	0x0028 00040 (<autogenerated>:1)	JNE	165
> 	0x002a 00042 (<autogenerated>:1)	NOP
> 	0x002a 00042 (<autogenerated>:1)	FUNCDATA	$0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
> 	0x002a 00042 (<autogenerated>:1)	FUNCDATA	$1, gclocals·2589ca35330fc0fce83503f4569854a0(SB)
> 	0x002a 00042 (<autogenerated>:1)	FUNCDATA	$3, "".(*Bar).Print.stkobj(SB)
> 	0x002a 00042 (<autogenerated>:1)	CMPQ	""..this+96(SP), $0
> 	0x0030 00048 (<autogenerated>:1)	JEQ	148
> 	0x0032 00050 (<unknown line number>)	NOP
> 	0x0032 00050 (bar.go:8)	XORPS	X0, X0
> 	0x0035 00053 (bar.go:8)	MOVUPS	X0, ""..autotmp_13+64(SP)
> 	0x003a 00058 (bar.go:8)	LEAQ	type.string(SB), AX
> 	0x0041 00065 (bar.go:8)	MOVQ	AX, ""..autotmp_13+64(SP)
> 	0x0046 00070 (bar.go:8)	LEAQ	""..stmp_2(SB), AX
> 	0x004d 00077 (bar.go:8)	MOVQ	AX, ""..autotmp_13+72(SP)
> 	0x0052 00082 (<unknown line number>)	NOP
> 	0x0052 00082 ($GOROOT/src/fmt/print.go:274)	MOVQ	os.Stdout(SB), AX
> 	0x0059 00089 ($GOROOT/src/fmt/print.go:274)	LEAQ	go.itab.*os.File,io.Writer(SB), CX
> 	0x0060 00096 ($GOROOT/src/fmt/print.go:274)	MOVQ	CX, (SP)
> 	0x0064 00100 ($GOROOT/src/fmt/print.go:274)	MOVQ	AX, 8(SP)
> 	0x0069 00105 ($GOROOT/src/fmt/print.go:274)	LEAQ	""..autotmp_13+64(SP), AX
> 	0x006e 00110 ($GOROOT/src/fmt/print.go:274)	MOVQ	AX, 16(SP)
> 	0x0073 00115 ($GOROOT/src/fmt/print.go:274)	MOVQ	$1, 24(SP)
> 	0x007c 00124 ($GOROOT/src/fmt/print.go:274)	MOVQ	$1, 32(SP)
> 	0x0085 00133 ($GOROOT/src/fmt/print.go:274)	PCDATA	$1, $1
> 	0x0085 00133 ($GOROOT/src/fmt/print.go:274)	CALL	fmt.Fprintln(SB)
> 	0x008a 00138 (bar.go:8)	MOVQ	80(SP), BP
> 	0x008f 00143 (bar.go:8)	ADDQ	$88, SP
> 	0x0093 00147 (bar.go:8)	RET
> 	0x0094 00148 (<autogenerated>:1)	CALL	runtime.panicwrap(SB)
> 	0x0099 00153 (<autogenerated>:1)	XCHGL	AX, AX
> 	0x009a 00154 (<autogenerated>:1)	NOP
> 	0x009a 00154 (<autogenerated>:1)	PCDATA	$1, $-1
> 	0x009a 00154 (<autogenerated>:1)	PCDATA	$0, $-2
> 	0x009a 00154 (<autogenerated>:1)	CALL	runtime.morestack_noctxt(SB)
> 	0x009f 00159 (<autogenerated>:1)	PCDATA	$0, $-1
> 	0x009f 00159 (<autogenerated>:1)	NOP
> 	0x00a0 00160 (<autogenerated>:1)	JMP	0
> 	0x00a5 00165 (<autogenerated>:1)	LEAQ	96(SP), DI
> 	0x00aa 00170 (<autogenerated>:1)	CMPQ	(BX), DI
> 	0x00ad 00173 (<autogenerated>:1)	JNE	42
> 	0x00b3 00179 (<autogenerated>:1)	MOVQ	SP, (BX)
> 	0x00b6 00182 (<autogenerated>:1)	JMP	42
> 	0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 0f 86 87  eH..%....H;a....
> 	0x0010 00 00 00 48 83 ec 58 48 89 6c 24 50 48 8d 6c 24  ...H..XH.l$PH.l$
> 	0x0020 50 48 8b 59 20 48 85 db 75 7b 48 83 7c 24 60 00  PH.Y H..u{H.|$`.
> 	0x0030 74 62 0f 57 c0 0f 11 44 24 40 48 8d 05 00 00 00  tb.W...D$@H.....
> 	0x0040 00 48 89 44 24 40 48 8d 05 00 00 00 00 48 89 44  .H.D$@H......H.D
> 	0x0050 24 48 48 8b 05 00 00 00 00 48 8d 0d 00 00 00 00  $HH......H......
> 	0x0060 48 89 0c 24 48 89 44 24 08 48 8d 44 24 40 48 89  H..$H.D$.H.D$@H.
> 	0x0070 44 24 10 48 c7 44 24 18 01 00 00 00 48 c7 44 24  D$.H.D$.....H.D$
> 	0x0080 20 01 00 00 00 e8 00 00 00 00 48 8b 6c 24 50 48   .........H.l$PH
> 	0x0090 83 c4 58 c3 e8 00 00 00 00 90 e8 00 00 00 00 90  ..X.............
> 	0x00a0 e9 5b ff ff ff 48 8d 7c 24 60 48 39 3b 0f 85 77  .[...H.|$`H9;..w
> 	0x00b0 ff ff ff 48 89 23 e9 6f ff ff ff                 ...H.#.o...
> 	rel 5+4 t=17 TLS+0
> 	rel 61+4 t=16 type.string+0
> 	rel 73+4 t=16 ""..stmp_2+0
> 	rel 85+4 t=16 os.Stdout+0
> 	rel 92+4 t=16 go.itab.*os.File,io.Writer+0
> 	rel 134+4 t=8 fmt.Fprintln+0
> 	rel 149+4 t=8 runtime.panicwrap+0
> 	rel 155+4 t=8 runtime.morestack_noctxt+0
139,143c213,217
< go.info."".(*Bar).Print$abstract SDWARFINFO dupok size=26
< 	0x0000 04 2e 28 2a 42 61 72 29 2e 50 72 69 6e 74 00 01  ..(*Bar).Print..
< 	0x0010 01 11 62 00 00 00 00 00 00 00                    ..b.......
< 	rel 0+0 t=24 type.*"".Bar+0
< 	rel 21+4 t=29 go.info.*"".Bar+0
---
> go.info."".Bar.Print$abstract SDWARFINFO dupok size=23
> 	0x0000 04 2e 42 61 72 2e 50 72 69 6e 74 00 01 01 11 62  ..Bar.Print....b
> 	0x0010 00 00 00 00 00 00 00                             .......
> 	rel 0+0 t=24 type."".Bar+0
> 	rel 18+4 t=29 go.info."".Bar+0
297c371,392
< type."".Bar SRODATA size=96
---
> type..namedata.*func(main.Bar)- SRODATA dupok size=18
> 	0x0000 00 00 0f 2a 66 75 6e 63 28 6d 61 69 6e 2e 42 61  ...*func(main.Ba
> 	0x0010 72 29                                            r)
> type.*func("".Bar) SRODATA dupok size=56
> 	0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
> 	0x0010 7f 95 9a 2f 08 08 08 36 00 00 00 00 00 00 00 00  .../...6........
> 	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
> 	0x0030 00 00 00 00 00 00 00 00                          ........
> 	rel 24+8 t=1 runtime.memequal64·f+0
> 	rel 32+8 t=1 runtime.gcbits.01+0
> 	rel 40+4 t=5 type..namedata.*func(main.Bar)-+0
> 	rel 48+8 t=1 type.func("".Bar)+0
> type.func("".Bar) SRODATA dupok size=64
> 	0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
> 	0x0010 b4 2e bc 27 02 08 08 33 00 00 00 00 00 00 00 00  ...'...3........
> 	0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
> 	0x0030 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
> 	rel 32+8 t=1 runtime.gcbits.01+0
> 	rel 40+4 t=5 type..namedata.*func(main.Bar)-+0
> 	rel 44+4 t=6 type.*func("".Bar)+0
> 	rel 56+8 t=1 type."".Bar+0
> type."".Bar SRODATA size=112
303c398,399
< 	0x0050 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
---
> 	0x0050 00 00 00 00 01 00 01 00 10 00 00 00 00 00 00 00  ................
> 	0x0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
309a406,409
> 	rel 96+4 t=5 type..namedata.Print.+0
> 	rel 100+4 t=25 type.func()+0
> 	rel 104+4 t=25 "".(*Bar).Print+0
> 	rel 108+4 t=25 "".Bar.Print+0
320a421,423
> ""..stmp_2 SRODATA size=16
> 	0x0000 00 00 00 00 00 00 00 00 0c 00 00 00 00 00 00 00  ................
> 	rel 0+8 t=1 go.string."debosmit ray"+0
325,326c428,429
< gclocals·2a5305abe05176240e61b8620e19a815 SRODATA dupok size=9
< 	0x0000 01 00 00 00 01 00 00 00 00                       .........
---
> gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
> 	0x0000 01 00 00 00 00 00 00 00                          ........
329c432
< "".(*Bar).Print.stkobj SRODATA size=24
---
> "".Bar.Print.stkobj SRODATA size=24
333,334d435
< gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
< 	0x0000 01 00 00 00 00 00 00 00                          ........
338a440,447
> gclocals·1a65e721a2ccc325b382662e7ffee780 SRODATA dupok size=10
> 	0x0000 02 00 00 00 01 00 00 00 01 00                    ..........
> gclocals·2589ca35330fc0fce83503f4569854a0 SRODATA dupok size=10
> 	0x0000 02 00 00 00 02 00 00 00 00 00                    ..........
> "".(*Bar).Print.stkobj SRODATA dupok size=24
> 	0x0000 01 00 00 00 00 00 00 00 f0 ff ff ff ff ff ff ff  ................
> 	0x0010 00 00 00 00 00 00 00 00                          ........
> 	rel 16+8 t=1 type.[1]interface {}+0

Here, it is evident that for the value method case:

  1. a copy of b was created due to the b.Print() invocation
  2. a copy of the string rel 0+8 t=1 go.string."debosmit ray"+0 was set on the copied struct.

Therefore, this further concretizes that when you use value pointers:

  1. a copy of the object is made when you invoke methods on that object
  2. any mutations to internal state, will only reflect on that copy of the object

Solution 4 - Go

Go passes arguments by value, not by reference, unless you use a pointer. So inside the function, you're not modifying s in any outer scope if you simply pass by value. However, when you pass a pointer, you're able to modify the "real" variable rather than just the copy that exists inside the function.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJayView Question on Stackoverflow
Solution 1 - GoVonCView Answer on Stackoverflow
Solution 2 - GofuzView Answer on Stackoverflow
Solution 3 - GoDebosmit RayView Answer on Stackoverflow
Solution 4 - GoTodd A. JacobsView Answer on Stackoverflow