58

I am new to Golang so allocation in it makes me insane:

import "sync"

type SyncMap struct {
        lock *sync.RWMutex
        hm map[string]string
}
func (m *SyncMap) Put (k, v string) {
        m.lock.Lock()
        defer m.lock.Unlock()

        m.hm[k] = v, true
}

and later, I just call:

sm := new(SyncMap)
sm.Put("Test, "Test")

At this moment I get a nil pointer panic.

I've worked around it by using another one function, and calling it right after new():

func (m *SyncMap) Init() {
        m.hm = make(map[string]string)
        m.lock = new(sync.RWMutex)
}

But I wonder, if it's possible to get rid of this boilerplate initializing?

Illarion Kovalchuk
  • 5,774
  • 8
  • 42
  • 54
  • `m.hm[k] = v, true` this makes no sense (attempting to assign two values to one variable), how did this even compile when the question was asked? – Nic Jun 22 '21 at 11:52

3 Answers3

70

You just need a constructor. A common used pattern is

func NewSyncMap() *SyncMap {
    return &SyncMap{hm: make(map[string]string)}
}

In case of more fields inside your struct, starting a goroutine as backend, or registering a finalizer everything could be done in this constructor.

func NewSyncMap() *SyncMap {
    sm := SyncMap{
        hm: make(map[string]string),
        foo: "Bar",
    }

    runtime.SetFinalizer(sm, (*SyncMap).stop)

    go sm.backend()

    return &sm
}
Josh Lindsey
  • 8,455
  • 3
  • 24
  • 25
themue
  • 7,626
  • 2
  • 25
  • 28
  • 1
    Thanks a lot! Now I remeber, that there was something about constructor in tutorial, but, being a Java devloper, I thought it should be related to new operator, not to New... code convention – Illarion Kovalchuk Dec 21 '10 at 13:00
  • 1
    This will work, but is not the best advice. The RWMutex should be included as a value, not as a pointer. Its zero value is a ready-to-use mutex, and this way you can avoid an explicit constructor function. – kelnos Jan 13 '13 at 01:22
  • Should have taken different names as it just is an example. As you'll see I also initialize the field 'foo' which is not part of the original struct. ;) – themue Jan 14 '13 at 10:49
  • 1
    Will the compiler always allocate structs such as this the one pointed to by NewSyncMap() on the heap? In other words, to achieve C++ object initialization on the stack, must we instead create an `Init` function that modifies an existing struct? – mk12 Jul 11 '13 at 23:58
11

The solution of 'Mue' doesn't work since the mutex is not initialized. The following modification works:

package main

import "sync"

type SyncMap struct {
        lock *sync.RWMutex
        hm map[string]string
}

func NewSyncMap() *SyncMap {
        return &SyncMap{lock: new(sync.RWMutex), hm: make(map[string]string)}
}

func (m *SyncMap) Put (k, v string) {
        m.lock.Lock()
        defer m.lock.Unlock()
        m.hm[k] = v
}

func main() {
    sm := NewSyncMap()
    sm.Put("Test", "Test")
}

http://play.golang.org/p/n-jQKWtEy5

deamon
  • 89,107
  • 111
  • 320
  • 448
  • 1
    Thank you so much! The uninitialized mutex results in a subtle error that is very hard to debug. The calls to Lock() and Unlock() succeed, but access is not synchronized. – Steve May 12 '17 at 06:33
5

Good catch by deamon. Mue was possibly thinking of the more common pattern of including the lock as a value rather than a pointer. Since the zero value of a Mutex is a ready-to-use unlocked Mutex, it requires no initialization and including one as a value is common. As a further simplification, you can embed it by omitting the field name. Your struct then acquires the method set of the Mutex. See this working example, http://play.golang.org/p/faO9six-Qx. Also I took out the use of defer. To some extent it's a matter of preference and coding style, but since it does have a small overhead, I tend not to use it in small functions, especially if there is no conditional code.

Sonia
  • 27,135
  • 8
  • 52
  • 54