2

When I reassign a new struct object to an existing variable, the address doesn't change. Code is shown below:

type Request struct {
    Field string
}
func main(){
    r := Request{Field: "a"}
    fmt.Printf("%p\n", &r)
    r = Request{Field: "b"}
    fmt.Printf("%p\n", &r)
}

Output:

0xc0004040d0
0xc0004040d0

It's just like the Feild was modified without allocating new memory. So what does Go do when reassignment happens?

If I want to use sync.pool, can I put the obj to pool with the resetting just like r := Request{}? (I mean with this operation, the struct obj can be reused and won't be collected by gc.)

icza
  • 389,944
  • 63
  • 907
  • 827
Alexander
  • 523
  • 5
  • 21
  • 2
    The language does not specify what happens under the hood if you do `r = Request{Field: "b"}` (except that r's value changes and of course r's address is unchanged by assigning a new value.) What this has to do with sync pool? I have no idea what you are asking here. – Volker May 27 '20 at 09:29

1 Answers1

3

Spec: Composite literals only states that when you take the address of a composite literal, that will point to an unnamed variable, so that requires allocation:

Taking the address of a composite literal generates a pointer to a unique variable initialized with the literal's value.

When you don't take the address of the literal just assign it, no allocation is required, the struct value can be assigned to the variable that already has memory allocated.

To verify, we may use Go's testing framework. Create a test file:

package main

import (
    "testing"
)

type Request struct {
    Field string
}

var r = Request{Field: "a"}

func BenchmarkStruct(b *testing.B) {
    for i := 0; i < b.N; i++ {
        r = Request{Field: "b"}
    }
}

var p = &Request{Field: "a"}

func BenchmarkStructPtr(b *testing.B) {
    for i := 0; i < b.N; i++ {
        p = &Request{Field: "b"}
    }
}

Run it with:

go test -bench . -benchmem

Output:

BenchmarkStruct-4       1000000000       0.948 ns/op       0 B/op    0 allocs/op
BenchmarkStructPtr-4    32160099        37.3 ns/op        16 B/op    1 allocs/op

As you can see, assigning a value of your Request struct using a composite literal requires no allocation. Taking its address and assigning that requires 16 bytes allocation (on my 64-bit architecture), this is the size of your Request struct which contains a single field of string type, and the string header is a pointer (8 bytes) and a length (8 bytes).

Assigning values in Go always makes a copy. So when you assign any value (including a struct value), the value will be copied and the original value is not referenced by the variable you assigned it to.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks very much!!! And as you said `the original value is not referenced by the variable you assigned it to`, does it mean that the original value will be collected by gc? – Alexander May 27 '20 at 12:10
  • @Alexander If there are no one else referring to it, then yes, eventually. – icza May 27 '20 at 12:27
  • @Alexander actually, the "original" value has the same memory as the "new" value if you only assign a struct to it (not when you assign the pointer; that allocates a new piece of memory, replaces the variable's pointer and garbage eventually connects the original allocation). This is because the new literal can be copied byte-by-byte to the variable's memory so it's in fact just assigned to the variable or the variable's fields in case it's a struct. This type of VALUE assignment is converted to constant values in the programme's byte code so no new memory is allocated. – Luis Paulo Jun 21 '22 at 02:04