Difference using pointer in struct fields

11,646

Solution 1

Right, there's a number of things to consider. First up: let's start with the obvious syntax error in your pointer example:

type Employee struct {
    FirstName *string `json:"name"`
    Salary    *int    `json:"salary"`
    FullTime  *bool   `json:"fullTime"`
}

So I've moved the asterisk to the type, and I've captialized the fields. The encoding/json package uses reflection to set the values of the fields, so they need to be exported.

Seeing as you're using json tags, let's start with the simple things:

type Foo struct {
    Bar string  `json:"bar"`
    Foo *string `json:"foo,omitempty"`
}

When I'm unmarshalling a message that has no bar value, the Bar field will just be an empty string. That makes it kind of hard to work out whether or not the field was sent or not. Especially when dealing with integers: how do I tell the difference between a field that wasn't sent vs a field that was sent with a value of 0?
Using a pointer field, and specify omitempty allows you to do that. If the field wasn't specified in the JSON data, then the field in your struct will be nil, if not: it'll point to an integer of value 0.

Of course, having to check for pointers being nil can be tedious, it makes code more error-prone, and so you only need to do so if there's an actual reason why you'd want to differentiate between a field not being set, and a zero value.


pitfalls

Pointers allow you to change values of what they point to

Let's move on to the risks pointers inherently bring with them. Assuming your Employee struct with pointer fields, and a type called EmployeeV that is the same but with value fields, consider these functions:

func (e Employee) SetName(name string) {
    if e.Firstname == nil {
        e.Firstname = &name
        return
    }
    *e.Firstname = name
}

Now this function is only going to work half of the time. You're calling SetName on a value receiver. If Firstname is nil, then you're going to set the pointer on a copy of your original variable, and your variable will not reflect the change you made in the function. If Firstname was set, however, the copy will point to the same string as your original variable, and the value that pointer points to will get updated. That's bad.

Implement the same function on EmployeeV, however:

func (e EmployeeV) SetName(name string) {
    e.Firstname = name
}

And it simply won't ever work. You'll always update a copy, and the changes won't affect the variable on which you call the SetName function. For that reason, the idiomatic way, in go, to do something like this would be:

type Employee struct {
    Firstname string
    // other fields
}

func (e *Employee) SetName(name string) {
    e.Firstname = name
}

So we're changing the method to use a pointer receiver.

Data races

As always: if you're using pointers, you're essentially allowing code to manipulate the memory something points to directly. Given how golang is a language that is known to facilitate concurrency, and accessing the same bit of memory means you're at risk of creating data-races:

func main() {
    n := "name"
    e := Employee{
        Firstname: &n,
    }
    go func() {
         *e.Firstname = "foo"
    }()
    race(e)
}

func race(e Employee) {
    go race(e)
    go func() {
        *e.Firstname = "in routine"
    }()
    *e.Firstname = fmt.Sprintf("%d", time.Now().UnixNano())
}

This Firstname field is accessed in a lot of different routines. What will be its eventual value? Do you even know? The golang race detector will most likely flag this code as a potential data race.


In terms of memory use: individual fields like ints or bools really aren't the thing you ought to be worried about. If you're passing around a sizeable struct, and you know it's safe, then it's probably a good idea to pass around a pointer to said struct. Then again, accessing values through a pointer rather than accessing them directly isn't free: indirection adds a small overhead.

Solution 2

We use pointers to share data, but that doesn't always mean it is more memory efficient or more performant. Go is extremely good and fast at copying data.

When it comes to structs a common reason for using pointers is that pointers can have nil values, where primitives can't. If you need a struct with optionals field, you'd use pointers

If you are deserialising JSON then you could omit fields using omitempty. Here fullTime is optional

type Employee struct {
    firstName string `json:"name"`
    salary int `json:"salary"`
    fullTime *bool `json:"fullTime,omitempty"`
}

Performance when using JSON

If you are deserializing JSON into pointers in the hopes of saving memory, you won't. From a JSON point of view each item is unique, so there is no sharing of data. You will use more memory, because each value now has to store a value and a pointer to the value. And it will be slower because you will need to dereference pointers the whole time

Solution 3

i recommend to read this post a link

Share:
11,646
omurbek
Author by

omurbek

Building web apps and saas services with golang and MEAN stack

Updated on June 05, 2022

Comments

  • omurbek
    omurbek almost 2 years

    We can create structs in golang this way. Examples below: What are differences between these two?

    // Usual way
    type Employee struct {
        firstName string    `json:"name"`
        salary    int       `json:"salary"`
        fullTime  bool      `json:"fullTime"`
        projects  []Project `json:"projects"`
    }
    
    // Un-usal way with pointers
    type Employee struct {
        firstName *string    `json:"name"`
        salary    *int       `json:"salary"`
        fullTime  *bool      `json:"fullTime"`
        projects  *[]Project `json:"projects"`
    }
    

    Are there any trade-offs like memory?

    Update:

    Assume below function:

    // this function consumes MORE memory
    func printEmployeeWithoutPointer(employee Employee) {
        // print here
    }
    
    // this function consumes LESS memory
    func printEmployeeWithPointer(employee *Employee) {
        // print here
    }
    
  • omurbek
    omurbek over 4 years
    I have updated Employee struct by adding projects field. In pointer version it hold reference to the array, but in non-pointer version it holds all projects. If the projects are very large (1000000), will it consume more memory when passing Employee to another function?
  • Leon
    Leon over 4 years
    @OmurbekKadyrbekov Is the project data shared or does it come from JSON?
  • omurbek
    omurbek over 4 years
    It comes from JSON, rest api
  • Leon
    Leon over 4 years
    Then my answer stands, a pointer will actually make performance worse
  • mkopriva
    mkopriva over 4 years
    @OmurbekKadyrbekov slices are headers with a pointer to the underlying array and the len and cap info (blog.golang.org/go-slices-usage-and-internals). Do not use poniters to slices for memory efficiency reasons.