LoginSignup
1
0

More than 1 year has passed since last update.

Go 副作用を期待して関数にSliceを渡す際の注意点

Last updated at Posted at 2023-04-04

概要

Goの sliceは参照渡しだがややこしい

  • 前提として Goのsliceは一応参照渡しである
  • 参照渡しなので、呼び出し先の関数内の更新は、呼び出し元に反映される (sli[0] = "updated" 等)
  • しかし goの組み込み関数 appendを使った更新は呼び出し元に反映されない
    • 理由は appendによる更新が、単なる変数の再代入で実現されているからである(sli = append(sli, "appended") 等)
    • このため呼び出し元とヒープで共有するメモリには一切変更が入らない

テストコード

下記のテストコードでは下記の 3種類の関数を呼び出し、呼び出し元への影響を確認している

  • 変数の再代入による更新
  • 組み込み関数 appendを用いた更新
  • 参照に閉じた更新
import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestSlice(t *testing.T) {
	// 要素3を持つ sliceを3つ用意
	sli1, sli2, sli3 :=
		[]int{1, 2, 3},
		[]int{1, 2, 3},
		[]int{1, 2, 3}

	// 1:変数の更新, 2:append更新, 3:index指定の更新 を適用
	f1, f2, f3 :=
		func(sl []int) { sl = []int{4, 5, 6} },
		func(sl []int) { sl = append(sl, 4) },
		func(sl []int) { sl[2] = 5 }

	// 変数の更新
	// -> 呼び出し元に影響なし
	f1(sli1)
	assert.Equal(t, []int{1, 2, 3}, sli1)

	// appendによる更新(変数の更新)
	// -> 呼び出し元に影響なし
	f2(sli2)
	assert.Equal(t, []int{1, 2, 3}, sli2)

	// index指定の更新(参照先の更新)
	// -> 呼び出し元に影響あり(sort.Ints(), reverse()等も同様の仕組み)
	f3(sli3)
	assert.Equal(t, []int{1, 2, 5}, sli3)
}

学び

  • 要素数の変更の無い更新であれば関数に副作用を適用できる
  • 要素数の変更の無い更新とは、具体的にはソートやリバース等の並べ替えのことである
  • 具体例としてsortパッケージの sort.Ints([]int) 等が当てはまる
  • 例外系の方が多いので、一般的な更新系の関数は引数にコレクションを渡し副作用を期待する形式より、結果を戻り値に戻す形式を採用した方が無難そうだ
1
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
1
0