Francis
Francis

Reputation: 131

Initialize pointer receiver in pointer method Go

How can I initialize a pointer receiver with a pointer method?

package main

import "fmt"

type Person struct {
    name string
    age  int
}

func (p *Person) Born() {

    if nil == p {
        p = new(Person)
    }

}

func main() {

    var person *Person
    person.Born()
    if person == nil {
        fmt.Println("This person should be initialized. Why is that not the case?")
    }
    fmt.Println(person)
}

One would expect person to be initialized (zeroed) after calling .Born() method which is a pointer receiver. But that is not the case. Could someone shed some light on this?

Upvotes: 13

Views: 15342

Answers (4)

7urkm3n
7urkm3n

Reputation: 6311

NewPerson function can initialize as a person, nor using Person struct and Born method to get a new Person.

package main

import (
    "fmt"
    "time"
)

type Person struct {
    Name string
    Dob  time.Time
}

func NewPerson(name string, year, month, day int) *Person {
    return &Person{
        Name: name,
        Dob:  time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local),
    }
}

func (p *Person) GetAge() string {
    d := time.Since(p.Dob)
    return fmt.Sprintf("%s's age is %d", p.Name, int(d.Hours()/24/365))
}

func (p *Person) Born() {
    p.Name = "New born (unnamed)"
    p.Dob = time.Now()
}

func main() {
    joe := NewPerson("Joe", 1999, 12, 31)
    joeAge := joe.GetAge()
    fmt.Println(joeAge)

    newPerson := &Person{}
    newPerson.Born()
    newPersonAge := newPerson.GetAge()
    fmt.Println(newPersonAge)
}

Upvotes: 0

peterSO
peterSO

Reputation: 166588

Clearly, person will not be initialized by method Born. Parameters are passed by value through the assignment of arguments to parameters.

The Go Programming Language Specification

The type of a method is the type of a function with the receiver as first argument.

type Point struct{ x, y float64 }

func (p *Point) Scale(factor float64) {
  p.x *= factor
  p.y *= factor
}

For instance, the method Scale has type

func(p *Point, factor float64)

However, a function declared this way is not a method.

In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution. The return parameters of the function are passed by value back to the calling function when the function returns.

To illustrate, here are various forms of the Born method call. The scope of p is limited to the method or function call.

package main

import "fmt"

type Person struct {
    name string
    age  int
}

// Method
func (p *Person) Born() {
    if nil == p {
        p = new(Person)
    }
}

// Method as function
func Born(p *Person) {
    if nil == p {
        p = new(Person)
    }
}

func main() {

    // Initial method call form
    {
        var person *Person
        person.Born()
        fmt.Println(person)
    }

    // Equivalent method call by value form
    {
        var person *Person
        {
            p := person
            p.Born()
        }
        fmt.Println(person)
    }

    // Equivalent method call as function call form
    {
        var person *Person
        {
            p := person
            Born(p)
        }
        fmt.Println(person)
    }

    // Equivalent method call as inline function form
    {
        var person *Person
        {
            p := person
            if nil == p {
                p = new(Person)
            }
        }
        fmt.Println(person)
    }

}

Output:

<nil>
<nil>
<nil>
<nil>

Upvotes: 2

kostix
kostix

Reputation: 55463

I think what you need instead is "constructor" or "factory" function:

type Person struct {
    name string
    age  int
}

func NewPerson(name string) *Person {
    return &Person{
        name: name,
    }
}

person := NewPerson("John Doe")

Generally, it's advised to try to define your types in such a way so that their so-called "zero value"—the value a variable of this type gets when it's not explicitly initialized otherwise—is ready to be used right away. In your case, it's questionable whether the zero value for Person is sensible because it will have age of 0, which is perfectly reasonable, and name being an empty string, which may or may not be OK.

Upvotes: 1

Jonathan Hall
Jonathan Hall

Reputation: 79594

One would expect person to be initialized (zeroed) after calling .Born() method which is a pointer receiver.

Calling a method on a receiver assumes that the receiver is already initialized.

So you need to initialize it:

var person *Person
person = &Person{}  // Sets the pointer to point to an empty Person{} struct

Or in a single statement:

var person = &Person{}

Or shorthand:

person := &Person{}

The reason your intended self-initialization is failing:

func (p *Person) Born() {
    if nil == p {
        p = new(Person)
    }
}

Is that your new assignment to p is scoped to the Born() function, so outside the function it has no effect.

Upvotes: 20

Related Questions