0

I found that following code in Golang works:

type user struct {
  name *string
  email *string
}
func foo() (*user, error) {
  var myname string
  var myemail string
  // Some code here to fetch myname and myemail
  u := user{}
  u.name = &myname
  u.email = &myemail
  return &u, nil
}

I am aware that it is safe to return a pointer to local variable as a local variable will survive the scope of the function, so I am not concerned about returning &u from the function foo().

What I am concerned are the assignments:

  u.name = &myname
  u.email = &myemail

Is it just by chance that Go compiler assigns u.name and u.email in heap so that I am able to access it outside the function, or there is some guarantee it will always work (via pointer escape analysis mechanism)?

if the above code is not safe, I would fall back to something like following:

  u.name = new(string)
  *u.name = myname
  ...
Soumya Kanti
  • 1,429
  • 1
  • 17
  • 28
  • 4
    The language specification does not make a distinction between heap and stack allocated variables. Go just has variables. It is always legal to take the address of a variable. As an implementation detail, the compiler allocates some variables on the heap and others on the stack. – Charlie Tumahai Feb 11 '20 at 07:30
  • It is guaranteed that the compiler will make sure each return from the function will return a pointer to a distinct variable (memory block). – kostix Feb 11 '20 at 07:37
  • 4
    Unless you touch package `unsafe`, you don't have to worry about pointers. As long as you have a pointer to a memory area (variable), it will not be freed. – icza Feb 11 '20 at 07:44
  • Thanks for the comments. @icza I interpret your comment "As long as you have a pointer to a memory area..." as "As long as your pointer to a variable is in scope...". From all the comments above I can assume the code posted above is safe. – Soumya Kanti Feb 11 '20 at 07:48
  • 1
    I mean it like "as you as you have a pointer anywhere". – icza Feb 11 '20 at 08:06
  • 1
    Go is higher-level than C. If you take a pointer to something, you can use it. You never, ever have to worry about it freeing a value with a live pointer to it, no matter how you use it, no matter where you get it from, no matter what. There are no edge cases, no special cases. If it compiles, it will work. – Adrian Feb 11 '20 at 14:57
  • All, thanks for the comments. For the 'faceless' members who have downvoted this question, would they come up and explain why you have done so? It is very awful to downvote a valid, properly asked and formatted question without any explanation and will eventually cause StackOverflow to die! – Soumya Kanti Feb 13 '20 at 05:38

1 Answers1

1

I dug deep further and figured out from here and here how to investigate this further and satisfy my query that it is indeed safe to set pointer to a local variable in a struct which is itself being returned as a pointer from a function.

My program (line numbers printed for better understanding):

  1 package main
  2
  3 import "fmt"
  4
  5 type user struct {
  6   Name *string
  7 }
  8
  9 func setName() (*user) {
 10   myname := "soumya"
 11   u := user{}
 12   u.Name = &myname
 13   return &u
 14 }
 15
 16 func main() {
 17   v := setName()
 18   fmt.Println(*v.Name)
 19 }

Output of go run -gcflags='-m -m':

$ go run -gcflags='-m -m' main.go
./main.go:9:6: can inline setName as: func() *user { myname := "soumya"; u := user literal; u.Name = &myname; return &u }
./main.go:16:6: cannot inline main: function too complex: cost 93 exceeds budget 80
./main.go:17:15: inlining call to setName func() *user { myname := "soumya"; u := user literal; u.Name = &myname; return &u }
./main.go:18:14: inlining call to fmt.Println func(...interface {}) (int, error) { return fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) }
./main.go:13:10: &u escapes to heap
./main.go:13:10:        from ~r0 (return) at ./main.go:13:3
./main.go:11:3: moved to heap: u
./main.go:12:12: &myname escapes to heap
./main.go:12:12:        from u (dot-equals) at ./main.go:12:10
./main.go:12:12:        from &u (address-of) at ./main.go:13:10
./main.go:12:12:        from ~r0 (return) at ./main.go:13:3
./main.go:10:3: moved to heap: myname
./main.go:18:15: *v.Name escapes to heap
./main.go:18:15:        from ~arg0 (assign-pair) at ./main.go:18:14
./main.go:18:14: io.Writer(os.Stdout) escapes to heap
./main.go:18:14:        from io.Writer(os.Stdout) (passed to call[argument escapes]) at ./main.go:18:14
./main.go:17:15: main &myname does not escape
./main.go:17:15: main &u does not escape
./main.go:18:14: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape
soumya

The output states that &myname escapes to heap in line no. 12, hence it is safe. Further, it does not escape anymore after line no. 17.

Soumya Kanti
  • 1,429
  • 1
  • 17
  • 28