Original map is changed upon changing its copy

I’ve the playground example as:

package main

import "fmt"

type Service struct {
	name      string
	namespace string
	endpoints []string
	mappings  map[string]string
}

func main() {
	svc := Service{
		name:      "nginx",
		namespace: "default",
		endpoints: []string{
			"192.168.0.1",
			"192.168.0.2",
		},
		mappings: map[string]string{
			"pod-0": "192.168.0.1",
			"pod-1": "192.168.0.2",
		},
	}

	nginx := svc
	nginx.namespace = "ingress"
	nginx.endpoints = append(nginx.endpoints, "192.168.0.40")
	nginx.mappings["pod-2"] = "192.168.0.40"
	fmt.Printf("updated: %+v\noriginal: %+v\n", nginx, svc)
}

Where I’m having a copy of svc that is nginx but once I modified the mapping part of this copy, I noticed the mapping in the original item svc had been changed!

I guess the reason is that nginx copying the address of the mapping used at svc not the value

I need to:

  1. Understand how and why this us happening
  2. How to avoid this, and ensure changing the copy/clone is not going to impact the origin

Think of a map like this:

typeap struct {
    data *mapData
}

type mapData struct {
    // contains filtered or unexported fields
}

When you copy a map, it is a copy. It’s just that it contains a pointer, just like copying a slice still references the same elements.

If you want to copy the Service’s mappings to its own map, then you’ll need to write your own Copy or Clone function. Maybe this would work:

type Service struct {
	name      string
	namespace string
	endpoints []string
	mappings  map[string]string
}

func (s *Service) Clone() *Service {
	s2 := *s // "future-proofing" in case addl. fields added.
	s2.endpoints = make([]string, len(s.endpoints))
	copy(s2.endpoints, s.endpoints)
	s2.mappings = make(map[string]string, len(s.mappings))
	for k, v := range s.mappings {
		s2.mappings[k] = v
	}
	return &s2
1 Like

Thanks a lot, very appreciated.