Go version

go version go1.23.1 darwin/arm64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/admin/Library/Caches/go-build'
GOENV='/Users/admin/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/admin/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/admin/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.23.1/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.23.1/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.23.1'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/admin/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/Users/admin/hard/test-squad-rcon/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/8q/9530g_8d3zbgb84jqwx9k39w0000gn/T/go-build3830069734=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

package main

import (
    "fmt"
    "time"
)

type Callbacks struct {
    onData func(string)
}

func (r *Callbacks) print() {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            {
                fmt.Printf("OnData is nil? // %v\n", r.onData == nil)
                if r.onData != nil {
                    r.onData("data")
                }
            }

        }
    }
}

func (r *Callbacks) OnData(callback func(string)) {
    r.onData = callback
}

func main() {
    r := &Callbacks{}

    go r.print()

    r.OnData(func(data string) {
        fmt.Println(data)
    })

    for {}
}

What did you see happen?

The function r.OnData(...) will never be executed, I looked at the assembler code of the executable file, and there the call to this function was removed.

There are several solutions to the problem. 1) Use select instead of for. 2) Where we assign the callback in the structure, print some log data to the console (for example). This way the compiler will not remove the function from the executable file.

What did you expect to see?

I have synchronous code that must be executed without fail, but it does not. In any case, looking at the assembler code and the absence of a function call, you can verify this

Comment From: gabyhelp

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: randall77

Your program has a data race (run with -race), you should fix that as Go does not guarantee any semantics in the presence of races.

Probably the immediate problem is that there is no synchronization after your r.OnData call, so no goroutine is guaranteed to observe the write inside that call. So that write is deadcoded away.

If you want the write of one goroutine to be seen by another, some synchronization is required.

Closing, as not a bug in Go.

Comment From: iamalone98

Your program has a data race (run with -race), you should fix that as Go does not guarantee any semantics in the presence of races.

Probably the immediate problem is that there is no synchronization after your r.OnData call, so no goroutine is guaranteed to observe the write inside that call. So that write is deadcoded away.

If you want the write of one goroutine to be seen by another, some synchronization is required.

Closing, as not a bug in Go.

Why can such code fix the situation? At the same time, the -race shows the same problem.

func (r *Callbacks) OnData(callback func(string)) {
    fmt.Println("it worked!")
    r.onData = callback
}

Comment From: zigo101

Without synchronizations,, there is not any guarantee made here. It may behave as you expect, or not.