Goで参照型の変数に代入し値を変更したとき元の値に影響がある場合とない場合

(2020-04-25)

サンプル用struct。

package main

import (
  "encoding/json"
  "fmt"
)

type Data struct {
  Value  string
  ValueP *string `json:",omitempty"`
  Slice  []Data  `json:",omitempty"`
  SliceP []*Data `json:",omitempty"`
}

func NewData() *Data {
  return &Data{
    Value:  "no-changed",
    ValueP: &[]string{"no-changed"}[0],
    Slice: []Data{
      Data{
        Value:  "no-changed",
      },
      Data{
        Value:  "no-changed",
      },
    },
    SliceP: []*Data{
      &Data{
        Value:  "no-changed",
      },
    },
  }
}

参照を取って値を取る

アドレスは変わらず、そのフィールドを書き換えると当然元々の値も書き換わる。 各要素が順番に代入されるfor-rangeでのループも他と同じく参照型でないなら変わらず、参照型なら変わる。

func main() {
  s := NewData()
  fmt.Printf("%p %p %p\n", s, s.ValueP, s.Slice) // 0xc00009a000 0xc000010240 0xc00009c000

  s2tmp := &s
  s2 := *s2tmp
  fmt.Printf("%p %p %p\n", s2, s2.ValueP, s2.Slice) // 0xc00009a000 0xc000010240 0xc00009c000
  s2.Value = "changed"
  s2.ValueP = &[]string{"changed"}[0]
  s2.Slice[0] = Data{
    Value: "changed",
  }
  for _, slice := range s2.Slice {
    slice.Value = "changed"
  }
  for _, slice := range s2.SliceP {
    slice.Value = "changed"
  }
  b, _ := json.Marshal(s)
  fmt.Println(string(b))
  // {"Value":"changed","ValueP":"changed","Slice":[{"Value":"changed"},{"Value":"no-changed"}],"SliceP":[{"Value":"changed"}]}
}

値を取って参照を取る

アドレスが元と違うものになり、そのフィールドに代入してもそれが参照型かどうかにかかわらず元の値は変わらない。 ただし参照型のフィールドのアドレスはそのままなので、その要素は次のSlice[0]のように書き変わる。

func main() {
  s := NewData()
  fmt.Printf("%p %p %p\n", s, s.ValueP, s.Slice) // 0xc00009a000 0xc000010240 0xc00009c000

  s2tmp := *s
  s2 := &s2tmp
  fmt.Printf("%p %p %p\n", s2, s2.ValueP, s2.Slice) // 0xc00009a0a0 0xc000010240 0xc00009c000
  s2.Value = "changed"
  s2.ValueP = &[]string{"changed"}[0]
  s2.Slice[0] = Data{
    Value: "changed",
  }
  for _, slice := range s2.Slice {
    slice.Value = "changed"
  }
  for _, slice := range s2.SliceP {
    slice.Value = "changed"
  }
  b, _ := json.Marshal(s)
  fmt.Println(string(b))
  // {"Value":"no-changed","ValueP":"no-changed","Slice":[{"Value":"changed"},{"Value":"no-changed"}],"SliceP":[{"Value":"changed"}]}
}

再代入

参照型の変数に再代入してもその変数の参照先が変わるだけで元々の参照先は変わらない。 参照先の値に代入し置き換えることはできる。アドレスは変わっていない。

func main() {
  s := NewData()
  NoEffect(s)
  b, _ := json.Marshal(s)
  fmt.Printf("%p %s\n", s, string(b)) // 0xc00009a000 {"Value":"no-changed","ValueP":"no-changed","Slice":[{"Value":"no-changed"},{"Value":"no-changed"}],"SliceP":[{"Value":"no-changed"}]}
  Effect(s)
  b, _ = json.Marshal(s)
  fmt.Printf("%p %s\n", s, string(b)) // 0xc00009a000 {"Value":"changed","ValueP":"changed","Slice":[{"Value":"changed","ValueP":"changed"}]}
}

func NoEffect(s *Data) {
  s = &Data{
    Value:  "changed",
    ValueP: &[]string{"changed"}[0],
    Slice: []Data{
      Data{
        Value:  "changed",
        ValueP: &[]string{"changed"}[0],
      },
    },
  }
}

func Effect(s *Data) {
  *s = Data{
    Value:  "changed",
    ValueP: &[]string{"changed"}[0],
    Slice: []Data{
      Data{
        Value:  "changed",
        ValueP: &[]string{"changed"}[0],
      },
    },
  }
}