Why don't maps allocate on first store?

276 views
Skip to first unread message

Tom Limoncelli

unread,
Aug 9, 2026, 12:13:14 PM (7 days ago) Aug 9
to golang-nuts
Why don't maps allocate on the first attempt to store something in them?

We all know that maps need to be initialized:

var m map[string]string
m["foo"] = bar // panics on STORE

Why can't it allocate ON FIRST STORE instead of panicking?

I DO understand why they aren't allocated initially. The zero value is
nil, not map[T1]T2{}. That makes sense. Allocations should be
explicit.

However, this forces the developer into a situation where they must
either scatter "if m == nil" checks throughout the codebase or force
the allocation at creation time.

For example, in github.com/DNSControl/dnscontrol every RecordConfig{}
(the struct that stores a DNS record) includes a field Metadata:
map[string]string for storing key/value pairs. Few of the records will
ever store metadata. We don't want to preallocate potentially millions
of empty map structures. However it is annoying to have to check for
nil in the code that does use this feature.

I asked Google, "Are there feature requests in the Go project to
allocate a map on first storage rather than the current behavior which
is to panic?" and got answers that were not very satisfactory:

> Why Go Rejects Auto-Allocation for Maps
> Backwards Incompatibility: Changing nil maps to auto-allocate on write would break existing programs that rely on explicit initialization or specific nil safety checks.
> Performance Intent: Allowing nil maps to exist without allocating memory is an intentional optimization. Developers can declare map variables without incurring heap allocation costs until elements are actually needed.
> Consistency with Other Types: Unlike slices—where a nil slice is functionally identical to a zero-length slice and works with append-maps require an initialized hash-map data structure (hmap pointer) in the runtime before storing entries. Making nil maps auto-initialize would create unique behavior not shared across other reference types.
That's true for auto-allocation at creation, which is not what I'm proposing.

Those arguments apply if you auto-allocate at creation, but that's not
my proposal.

* The existing safety checks would still work because the zero value
is preserved.
* If someone wants to make() with a capacity hint, they can already do that.
* The runtime already does the check to determine if it should panic;
the user-code doing this is redundant.
* If someone needs to carefully control when allocations happen, they
aren't prevented from doing that. Existing code wouldn't break.
* It matches the symmetry for reading from a nil map. (which does not panic)

This seems like a win-win. It would reduce panics and be more
developer-friendly.

Tom

Jan Mercl

unread,
Aug 9, 2026, 12:56:52 PM (7 days ago) Aug 9
to Tom Limoncelli, golang-nuts
On Sun, Aug 9, 2026 at 6:12 PM Tom Limoncelli <t...@whatexit.org> wrote:
>
> Why don't maps allocate on the first attempt to store something in them?
>
> We all know that maps need to be initialized:
>
> var m map[string]string
> m["foo"] = bar // panics on STORE
>
> Why can't it allocate ON FIRST STORE instead of panicking?

Because https://go.dev/play/p/UbN46-c9iyi

robert engels

unread,
Aug 9, 2026, 1:15:56 PM (7 days ago) Aug 9
to Jan Mercl, Tom Limoncelli, golang-nuts
I don’t think that is correct. You would have the same problem if you allocated m after the m2 assignment.

> On Aug 9, 2026, at 11:56 AM, Jan Mercl <0xj...@gmail.com> wrote:
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/CAA40n-XS%3DUMmc9immQ%2Byguj4vQaa8xiLRVFuXrEheBR1f-aBVg%40mail.gmail.com.

Jan Mercl

unread,
Aug 9, 2026, 2:05:23 PM (7 days ago) Aug 9
to robert engels, Tom Limoncelli, golang-nuts
On Sun, Aug 9, 2026 at 7:15 PM robert engels <rob...@me.com> wrote:

> I don’t think that is correct.

The example demonstrated what would not work with OP's proposal
(https://go.dev/play/p/fV7BeVDj3FQ).

Def Ceb

unread,
Aug 9, 2026, 2:20:00 PM (7 days ago) Aug 9
to golan...@googlegroups.com
I don't see how this could be reasonably implemented without breaking
the Go 1 promise.
What would this even look like? Like this?

var m map[int]int // m is nil
m2 := m // m2 is, presumably, also nil
m[5] = 10 // m is no longer nil? what is m2 set to?

A map is a referential data structure. What happens to the copy of m
(m2) made before the first allocation? Would m2 stay as nil, or would it
also retroactively get set to the newly allocated m?
Either sound like they'd cause difficult to diagnose bugs. The latter
sounds more like your map now becomes some opaque default-allocated
object that pretends to be a simple nil until you poke it the right way.

Tom Limoncelli:

robert engels

unread,
Aug 9, 2026, 2:25:09 PM (7 days ago) Aug 9
to Jan Mercl, Tom Limoncelli, golang-nuts
But it’s the same issue. If you allocate after aliasing the code will crash using the current behavior.

> On Aug 9, 2026, at 1:05 PM, Jan Mercl <0xj...@gmail.com> wrote:

Jan Mercl

unread,
Aug 9, 2026, 2:32:35 PM (7 days ago) Aug 9
to robert engels, Tom Limoncelli, golang-nuts
On Sun, Aug 9, 2026 at 8:24 PM robert engels <rob...@me.com> wrote:

> But it’s the same issue. If you allocate after aliasing the code will crash using the current behavior.

Show what you mean ("the same issue") in code as well. The prose alone
is mostly opaque and/or ambiguous to me. Now for the second time.

robert engels

unread,
Aug 9, 2026, 2:44:14 PM (7 days ago) Aug 9
to Jan Mercl, Tom Limoncelli, golang-nuts

Jason E. Aten

unread,
Aug 9, 2026, 5:13:50 PM (7 days ago) Aug 9
to golang-nuts
On Sunday, August 9, 2026 at 1:13:14 PM UTC-3 Tom Limoncelli wrote:
For example, in github.com/DNSControl/dnscontrol every RecordConfig{}
(the struct that stores a DNS record) includes a field Metadata:
map[string]string for storing key/value pairs. Few of the records will
ever store metadata. We don't want to preallocate potentially millions
of empty map structures. However it is annoying to have to check for
nil in the code that does use this feature.

The tension here between "wanting to save alot of space" and "checking if space has been saved" 
seems fundamental. These are two sides of the same coin. 

Your best bet is to simply encapsulate access with getter and setter 
helper functions that do the nil checks for you, then use the helpers everywhere.
This is simple and straight forward.

func metaGet(m map[string]string, key string) (val string, ok bool) {
   if m == nil {
       return
   }
   val, ok = m[key]
   return
}

func metaSet(m map[string]string, key string, val string) {
    if m == nil {
         m = make(map[string]string)
    }
   m[key] = val
}

Jason E. Aten

unread,
Aug 9, 2026, 5:19:11 PM (7 days ago) Aug 9
to golang-nuts
ah, correction: metaSet would have to take m as a pointer to a map.

robert engels

unread,
Aug 10, 2026, 4:28:26 PM (6 days ago) Aug 10
to Jan Mercl, Tom Limoncelli, golang-nuts
So I provided the code to illustrate my argument and you didn’t respond? At least, “gee, it’s pretty obvious what you were saying, and you’re right, it’s the same problem”. 

On Aug 9, 2026, at 1:43 PM, robert engels <rob...@me.com> wrote:



Tom Limoncelli

unread,
Aug 10, 2026, 4:51:15 PM (6 days ago) Aug 10
to robert engels, Jan Mercl, golang-nuts
Thank you all for your feedback. I'm still trying to wrap my head around how m2 works when m is nil. I'll try more code examples later this week.

robert engels

unread,
Aug 10, 2026, 5:11:33 PM (6 days ago) Aug 10
to Tom Limoncelli, Jan Mercl, golang-nuts
It doesn’t work… see the code I shared.

> On Aug 10, 2026, at 3:51 PM, Tom Limoncelli <t...@whatexit.org> wrote:
>
> 

Def Ceb

unread,
Aug 10, 2026, 5:15:01 PM (6 days ago) Aug 10
to golan...@googlegroups.com
The fact that the concrete semantics of this are unclear, along with it
seemingly being incompatible with the Go 1 compatibility promise, makes
me doubt there's much of a chance of this being implemented.

Tom Limoncelli:
> Thank you all for your feedback. I'm still trying to wrap my head around
> how m2 works when m is nil. I'll try more code examples later this week.
>
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to golang-nuts...@googlegroups.com <mailto:golang-
> nuts+uns...@googlegroups.com>.
> To view this discussion visit https://groups.google.com/d/msgid/golang-
> nuts/
> CAHVFxg%3DNmMf40doo2PUAzzySaw55%3Dk5RbtJmZHt9_f_nfnrZrA%40mail.gmail.com
> <https://groups.google.com/d/msgid/golang-nuts/
> CAHVFxg%3DNmMf40doo2PUAzzySaw55%3Dk5RbtJmZHt9_f_nfnrZrA%40mail.gmail.com?utm_medium=email&utm_source=footer>.

Brian Candler

unread,
Aug 11, 2026, 5:52:52 AM (6 days ago) Aug 11
to golang-nuts
In Go, a map is essentially a pointer to a data structure.

A zero-valued map is thus like a zero-valued pointer. You would not expect

var ap *int

to allocate space for an int up-front; nor for

*ap = 1

to allocate an int  (and change ap to point to it) if ap is nil.

Like pointers, maps are passed by value.  So if you passed a nil map to a function, and the function dynamically allocated a map when inserting the first value, the caller would be no wiser; they'd still be holding a nil map. You'd have to return the new map, or pass a pointer to the original variable holding the map.

In a functional programming language, a map would be immutable and you'd always return a new map value in order to update it. That's cool, but it's not really Go's style.

Passing a pointer to a map is doable, but means special-casing wherever you want to do updates:

func foo(m *map[string]string) {
if *m == nil {
*m = make(map[string]string)
}
(*m)["foo"] = "bar"
}

I guess you could wrap this pattern: https://go.dev/play/p/VdH1W-Y5Dky
(I couldn't be bothered to try using generics). But in any case, it involves passing a pointer to a map everywhere, which means an extra level of indirection for every map access.

IMO it's simpler to say: "if you're passing a map into a function, and this function might modify the map, then the caller must allocate it first".  Note: passing a nil map to a function is still valid if you only expect it to read from the map, not update it.

Once a map has been allocated, its value doesn't change, so you can happily pass it around by value. The data structure changes as items are inserted and removed, but its location doesn't change, and the map value is just a pointer to that location.

Brian Candler

unread,
Aug 11, 2026, 6:11:03 AM (6 days ago) Aug 11
to golang-nuts
FWIW, this is how I'd deal with the motivating issue around RecordConfig{} and mostly-nil Metadata maps:


By making metadata a private member of the struct, you can only access it from outside the package via method Metadata() [which will allocate it if required] and MetadataReadonly() [which is allowed to return nil]
Reply all
Reply to author
Forward
0 new messages