differences between pointer and value slice in for-range loop

Issue

Please check this code snippet:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {
    fmt.Println("use values:")

    // use values in range loop and go rountines
    values := []field{{"one"},{"two"},{"three"}}
    for _, v := range values {
        go v.print()
    }

    time.Sleep(time.Second)

    fmt.Println()
    fmt.Println("use pointers:")

    // use pointers in range loop and go rountines
    poniters := []*field{{"one"},{"two"},{"three"}}
    for _, v := range poniters {
        go v.print()
    }

    time.Sleep(time.Second)
}

Link here: https://play.golang.org/p/cdryPmyWt5

The code above is going to check the differences between pointers and values in a for loop, while go statement is also used at the same time. For code:

values := []field{{"one"},{"two"},{"three"}}
for _, v := range values {
    go v.print()
}

we know that the console will print three three three as result, because for loop runs into its end before goroutines start executing, which write v as the last element of the slice. But what about pointers?

poniters := []*field{{"one"},{"two"},{"three"}}
for _, v := range poniters {
    go v.print()
}

It seems to print one two three, why?

Thanks.

Solution

A: The parameters are evaluated before the function is called. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution, so:

The first go v.print() is syntactic sugar for go (*field).print(&v) and
the second go v.print() is syntactic sugar for go (*field).print(v).

If first for loop finishes before goroutines start, &v is same for the calls and these three calls is all the same. See code 2 by adding time.Sleep(100) after go v.print() to first loop. or with go func(v field) { v.print() }(v) on The Go Playground (Code 3 with sync.WaitGroup).
Also, you have data race here (see B).

And for the second go (*field).print(v) here v is pointer and three goroutines parameter’s evaluated before calling print and have three different addresses.

1- Try this on The Go Playground:

package main

import (
    "fmt"
    "time"
)

type field struct {
    name string
}

func (p *field) print() {
    fmt.Println(p.name)
}

func main() {
    fmt.Println("use values:")

    // use values in range loop and go rountines
    values := []field{{"one"}, {"two"}, {"three"}}
    for _, v := range values {
        fmt.Println(&v)
        go (*field).print(&v) //go v.print()
    }

    time.Sleep(time.Second)

    fmt.Println()
    fmt.Println("use pointers:")

    // use pointers in range loop and go rountines
    poniters := []*field{{"one"}, {"two"}, {"three"}}
    for _, v := range poniters {
        fmt.Println(v)
        go (*field).print(v) //go v.print()
    }

    time.Sleep(time.Second)
}

output:

use values:
&{one}
&{two}
&{three}
three
three
three

use pointers:
&{one}
&{two}
&{three}
two
one
three

2- Try this on The Go Playground:

package main

import (
    "fmt"
    "time"
)

type field struct {
    name string
}

func (p *field) print() {
    fmt.Println(p.name)
}

func main() {
    fmt.Println("use values:")

    // use values in range loop and go rountines
    values := []field{{"one"}, {"two"}, {"three"}}
    for _, v := range values {
        fmt.Println(&v)
        go v.print() //go (*field).print(&v) //
        time.Sleep(100)
    }

    time.Sleep(time.Second)

    fmt.Println()
    fmt.Println("use pointers:")

    // use pointers in range loop and go rountines
    poniters := []*field{{"one"}, {"two"}, {"three"}}
    for _, v := range poniters {
        fmt.Println(v)
        go v.print() //go (*field).print(v) //
    }

    time.Sleep(time.Second)
}

output:

use values:
&{one}
one
&{two}
two
&{three}
three

use pointers:
&{one}
&{two}
&{three}
one
two
three

B: You have data race, try go build -race your code, then run generated file, WARNING: DATA RACE:

output:

use values:
==================
WARNING: DATA RACE
Read at 0x00c042030210 by goroutine 6:
  runtime.convT2E()
      Go/src/runtime/iface.go:155 +0x0
  main.(*field).print()
      .../m.go:14 +0x6c

Previous write at 0x00c042030210 by main goroutine:
  main.main()
      .../m.go:22 +0x1c3

Goroutine 6 (running) created at:
  main.main()
      .../m.go:23 +0x204
==================
two
three
three

use pointers:
one
two
three
Found 1 data race(s)

Answered By – user6169399

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.