Why should constructor of Go return address?
Asked Answered
S

2

18

I understand that Go doesn't have any constructors and a New func is used in its place, but according to this example.

func NewFile(fd int, name string) *File {
  if fd < 0 {
    return nil
  }
  f := File{fd, name, nil, 0}
  return &f
}

They always return &f. Why just simply returning File isn't suffice?

Update

I've tried returning the created object for a simple struct and it's fine. So, I wonder if returning an address is a standard way of constructor or something.

Thanks.

Savadove answered 11/8, 2015 at 3:42 Comment(5)
The function returns a File*, so it is not possible to return File "directly". In the previous case new(File) was used to start with a File*.Yaelyager
So, I don't have to return address to the variable unless I'm using File, right?Savadove
Both examples use 'File' .. I don't know how to interpret that question.Yaelyager
In go, returning the address of a variable is the same as calling malloc() in C. If you don't need it you don't need to use it.Canonist
There's nothing about the language that requires the function to return a pointer, that was a choice the person defining the function made. Your question just generalizes to the question, "when should I use pointers, and when should I use plain structs?" The answer is, "it depends." You can do some research on the Internet on when people typically choose to use pointers in Go.Phila
W
26

As mentioned, yes, the spec allows you to return either values (as non-pointers) or pointers. It's just a decision you have to make.

When to return pointer?

Usually if the value you return is "more useful" as a pointer. When is it more useful?

For example if it has many methods with pointer receiver. Yes, you could store the return value in a variable and so it will be addressable and you can still call its methods that have pointer receivers. But if a pointer is returned right away, you can "chain" method calls. See this example:

type My int

func (m *My) Str() string { return strconv.Itoa(int(*m)) }

func createMy(i int) My { return My(i) }

Now writing:

fmt.Println(createMy(12).Str())

Will result in error: cannot call pointer method on createMy(12)

But if works if you return a pointer:

func createMy(i int) *My { return (*My)(&i) }

Also if you store the returned value in a data structure which is not addressable (map for example), you cannot call methods on values by indexing a map because values of a map are not addressable.

See this example: My.Str() has pointer receiver. So if you try to do this:

m := map[int]My{0: My(12)}
m[0].Str() // Error!

You can't because "cannot take the address of m[0]". But the following works:

m := map[int]*My{}
my := My(12)
m[0] = &my // Store a pointer in the map

m[0].Str() // You can call it, no need to take the address of m[0]
           // as it is already a pointer

And another example for pointers being useful is if it is a "big" struct which will be passed around a lot. http.Request is a shining example. It is big, it is usually passed around a lot to other handlers, and it has methods with pointer receiver.

If you return a pointer, that usually suggests that the returned value is better if stored and passed around as a pointer.

Wolk answered 11/8, 2015 at 5:55 Comment(0)
P
0

Pointer receiver accepts both pointer and value types, as long as it matches the data type.

type User struct {
    name  string
    email string
    age   int
}

// NewUserV returns value ... ideally for a User we should not be 
// returning value
func NewUserV(name, email string, age int) User {
    return User{name, email, age}
}

// NewUserP returns pointer ...
func NewUserP(name, email string, age int) *User {
    return &User{name, email, age}
}

// ChangeEmail ...
func (u *User) ChangeEmail(newEmail string) {
    u.email = newEmail
}

func main() {
    // with value type
    usr1 := NewUserV("frank", "[email protected]", 22)
    fmt.Println("Before change: ", usr1)
    usr1.ChangeEmail("[email protected]")
    fmt.Println("After change: ", usr1)

    // with pointer type
    usr2 := NewUserP("john", "[email protected]", 22)
    fmt.Println("Before change: ", usr2)
    usr2.ChangeEmail("[email protected]")
    fmt.Println("After change: ", usr2)

}

In addition to what icza mentioned about the big struct being passed around. Pointer values are a way of saying that pointer semantics are at play and who ever uses the particular type should not make copy of the value which is being shared by the pointer.

If you look at the struct of File or http type, it maintains channels or some other pointer types which is unique to that value. Make a copy of the value (given to you by the pointer) would lead to hard to find bugs since the copied value might end up writing or reading to the pointer types of the original value.

Prosecutor answered 24/3, 2020 at 22:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.