LoginSignup
0
0

More than 5 years have passed since last update.

スライスのレシーバの挙動

Posted at

スライスの先頭要素に値を追加するレシーバを作成する際に、ポインタ関連で詰まったので、その時の失敗例と成功例、その理由を書いておく。

やりたかったこと

以下のように、スライスの先頭に値を追加するレシーバを作成したい。

type Numbers []int

func (numbers Numbers) AddToHead(num int) {
    // 省略
}

func main() {
    nums := Numbers{0}
    fmt.Println(nums) // [0]
    nums.AddToHead(5)
    fmt.Println(nums) // [5 0]
}

失敗例

パターン1

パターン1
package main

import "fmt"

type Numbers []int

func (numbers Numbers) AddToHead(num int) {
    numbers = append(append(numbers[:0], num), numbers[0:]...)
}

func main() {
    nums := Numbers{0}
    fmt.Println(nums)
    for i := 1; i < 5; i++ {
        nums.AddToHead(i)
        fmt.Println(nums)
    }
}

これを実行すると

出力結果
[0]
[1]
[2]
[3]
[4]

となってしまう。

理由

AddToHead()内で、以下の処理が行われているため、上記の結果が出力される。

  1. 値レシーバなので、numsnumbersにコピーされる。この時、&nums != &numbersだが、内部の要素は参照渡しとなるため、&nums[0] == &numbers[0]
  2. append(numbers[:0], num)が実行され、numbers[:0]の隣、すなわちnumbers[0]numが追加される。この時点で、nums[0] = numとなる。
  3. append(append(numbers[:0], num), numbers[0:]...)が実行されるが、この後実行される処理はnumbersを対象に行われており、numbersとアドレスが違うnumsは影響を受けない。

上記の処理により、nums[0] = numに書き換えが行われただけで、numsに追加処理がされていないため、上記のような出力が得られる。

パターン2

パターン2
package main

import "fmt"

type Numbers []int

func (numbers Numbers) AddToHead(num int) {
    tmp := append(Numbers{num}, numbers[0:]...)
    numbers = append(numbers[:0], tmp...)
}

func main() {
    numbers := Numbers{0}
    fmt.Println(numbers)
    for i := 1; i < 5; i++ {
        numbers.AddToHead(i)
        fmt.Println(numbers)
    }
}

これを実行すると

出力結果
[0]
[0]
[0]
[0]
[0]

となってしまう。

理由

AddToHead()内で、以下の処理が行われているため、上記の結果が出力される。

  1. 値レシーバなので、numsnumbersにコピーされる。この時、&nums != &numbersだが、内部の要素は参照渡しとなるため、&nums[0] == &numbers[0]
  2. tmp := append(Numbers{num}, numbers[0:]...)が実行され、新たにNumbers{num}を生成し、それにnumbersの要素をコピーして追加する。
  3. numbers = append(numbers[:0], tmp...)が実行されるが、この処理はnumbersを対象に行われており、numbersとアドレスが違うnumsは影響を受けない。(この処理が行われた時点で、numbers[0]のアドレス、すなわちnums[0]のアドレスも失われたので、このレシーバ内からはnumsに干渉できない。)

上記の処理から、numsに対して何も行われないため、上記のような出力が得られる。

成功例

以上の失敗から、原因を考えた結果できた成功例。

成功例
package main

import "fmt"

type Numbers []int

func (numbers *Numbers) AddToHead(num int) {
    tmp := append(Numbers{num}, (*numbers)[0:]...)
    *numbers = append((*numbers)[:0], tmp...)
}

func main() {
    numbers := Numbers{0}
    fmt.Println(numbers)
    for i := 1; i < 5; i++ {
        numbers.AddToHead(i)
        fmt.Println(numbers)
    }
}

これを実行すると、

出力結果
[0]
[1 0]
[2 1 0]
[3 2 1 0]
[4 3 2 1 0]

の出力が得られ、目的を達成していることがわかる。

失敗例のパターン2から「値レシーバからポインタレシーバに変更」のみ変更を加えたもの。

理由

AddToHead()内で、以下の処理が行われているため、上記の結果が出力される。

  1. ポインタレシーバなので、numsのアドレスがnumbersにコピーされる。
  2. tmp := append(Numbers{num}, (*numbers)[0:]...)が実行され、新たにNumbers{num}を生成し、それに(*numbers)[0:]の要素をコピーして追加している。numsnumbsersが同一のアドレスなためnums[0:]の要素を追加していることになる。
  3. numbers = append(numbers[:0], tmp...)が実行され、numbersの先頭にtmpの要素を追加する。この処理はnumbersを対象に行われているが、同一アドレスのnumsも変更されている。

上記の処理から、numsに値が追加され上記のような出力が得られる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0