What difference there is when you access to another struct by value or by a pointer?
When should be used each one of them?
type foo_ struct {
st uint8
nd uint8
}
type bar struct {
rd uint8
foo foo_
}
type barP struct {
rd uint8
foo *foo_
}
For many Go developers, the systematic use of pointers to share structs instead of the copy itself seems the best option in terms of performance.
A struct is a value type, so it's always passed as a value. A value can either be a reference type (object) or a value type (struct). What's passed around is always a value; for a reference type you pass the value of the reference to it, for a value type you pass the value itself.
To access members of a structure using pointers, we use the -> operator. In this example, the address of person1 is stored in the personPtr pointer using personPtr = &person1; .
Pointer to structure holds the add of the entire structure. It is used to create complex data structures such as linked lists, trees, graphs and so on. The members of the structure can be accessed using a special operator called as an arrow operator ( -> ).
If you declare or allocate a variable of type bar
, you reserve and initialize to zero memory for both rd uint8
and foo foo_
. There is always one variable of type foo_
embedded in a variable of type bar
.
var b bar // declare b
If you declare or allocate a variable of type barP
, you reserve and initialize to zero memory for both rd uint8
and foo *foo_
. A zero value pointer is a nil
pointer. No variable of type foo_
is allocated; you must do that separately. There is either zero (foo == nil
) or one variable of type foo_
pointed to by a variable of type barP
. A variable of type barP
may point to the same variable of type foo_
as other variables of type barP
, sharing the same copy of the variable of type foo_
. A change to a shared copy is seen by all variables that point to it.
var bp barP // declare bp
bp.foo = new(foo_) // allocate bp.foo
Which one to use depends on the properties of type bar
versus type barP
. Which type more closely reflects the problem that you are trying to solve?
For example, consider this invoice problem. We always have a billing address; we are always going to ask for our money. However, we often ship to the billing address, but not always. If the shipping address is nil
, use the billing address. Otherwise, use a separate shipping address. We have two warehouses, and we always ship from one or the other. We can share the two warehouse locations. Since we don't send an invoice until the order is shipped from the warehouse, the warehouse location will never be nil
.
type address struct {
street string
city string
}
type warehouse struct {
address string
}
type invoice struct {
name string
billing address
shipping *address
warehouse *warehouse
}
The answer is largely independent of language - the equivalent in C has the same issues.
When you have an embedded value (as in bar
), then your structure is big enough to hold the complete sub-structure and the other part.
When you have a pointer to a value (as in barP
), then a number of structures of type barP
may share the same foo
. When any of the barP
modifies a part of the foo
it points to, it affects all the other barP
structures that point to the same place. Also, as the commentary suggests, you have to manage two separate objects - the barP
and the foo
as against one with the plain bar
type.
In some languages, you would have to worry about dangling pointers and uninitialized values etc; Go is garbage collected and generally more type-safe than other languages.
So, use a pointer when you want multiple barP
objects to share the same foo
object; otherwise, use an explicit member object, rather than a pointer to an object.
The Golang FAQ now summarizes the difference between:
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 are reference types, 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, ifpointerMethod
modifies the fields ofs
, the caller will see those changes, butvalueMethod
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.
By the way, pointer receivers are identical to the situation in Java, although in Java the pointers are hidden under the covers; it's Go's value receivers that are unusual.Second is the consideration of efficiency. If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.
(This efficiency point is also illustrated in "Memory, variables in memory, and pointers ")
Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With