0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go可変長引数の落とし穴と対処法

Last updated at Posted at 2025-09-10

golang 可変長引数 で検索しても本題の情報を得られなかったのでここにまとめます。

Go の可変長引数( variadic parameters )を使用する際に注意すべき重要な挙動と、安全なコーディング手法について解説します。

可変長引数の基本的な挙動

可変長引数を受け取る関数は、スライスに ... をつけて渡すとそのスライスが渡されます。

問題のあるコード

package main

import "fmt"

// processNumbers は可変長引数で受け取った値をそれぞれ2倍してスライスで返します。
func processNumbers(numbers ...int) []int {
	fmt.Printf("受け取ったスライス: %v\n", numbers)
	for i := range numbers {
		numbers[i] *= 2 // 各要素を2倍にする
	}
	fmt.Printf("返すスライス: %v\n", numbers)
    return numbers
}

個別の値を渡す場合

package main

import "fmt"

func main() {
	result := processNumbers(1, 2, 3) // 個別の値として渡す
	// この場合、関数を呼び出すときに新しいスライスが作成される
	fmt.Printf("実行結果: %v\n", result) // [2, 4, 6]
}

スライスに...をつけて渡す場合

package main

import "fmt"

func main() {
	original := []int{1, 2, 3}
	fmt.Printf("実行前: %v\n", original) // [1, 2, 3]
	result := processNumbers(original...) // スライスを展開して渡す
	// この場合、スライスがそのまま processNumbers に渡される。
	fmt.Printf("実行結果: %v\n", result) // [2, 4, 6]
	fmt.Printf("元のスライス: %v\n", original) // [2, 4, 6] え?
}

Playground で確認する

サンプルコード
package main

import "fmt"

func dangerousFunction(numbers ...int) []int {
	// 受け取ったスライスをそのまま変更してしまう
	for i := range numbers {
		numbers[i] *= 2 // 各要素を2倍にする
	}
	return numbers
}

func main() {
	fmt.Printf("ケース1: 個別の値を渡す場合(問題なし)\n")
	result1 := dangerousFunction(1, 2, 3)
	fmt.Printf("結果1: %v\n", result1) // [2, 4, 6]

	fmt.Printf("ケース2: スライスを...で渡す場合(問題あり)\n")
	original := []int{1, 2, 3}
	fmt.Printf("処理前: %v\n", original) // [1, 2, 3]
	result2 := dangerousFunction(original...)
	fmt.Printf("結果2: %v\n", result2)         // [2, 4, 6]
	fmt.Printf("元のスライス: %v\n", original) // [2, 4, 6] - 変更されてしまった!

	// 呼び出し元は元のスライスが変更されることを期待していない
	// これは予期しない副作用となる
}

関数内での判別は不可能

残念ながら、可変長引数を受け取る関数内で、引数が ... を使って渡されたスライスなのか、個別の値なのかを確実に判別する方法はありません。

package main

import "fmt"

func cannotDistinguish(args ...int) {
	// 個別の値を渡したときは容量は長さと同じになるらしいが、
	// 確実に判別できない
	if len(args) == cap(args) {
		fmt.Println("...で渡された可能性が高い(ただし確実ではない)")
	}
}

安全な対処法:必ずコピーを作成する

可変長引数で受け取ったスライスを変更する場合は、必ず事前にコピーを作成しましょう。

package main

import "slices"

func safeFunctionWithClone(numbers ...int) []int {
	// Go 1.21以降で利用可能
	result := slices.Clone(numbers)

	// コピーに対して変更を行う
	for i := range result {
		result[i] *= 2
	}

	return result
}

まとめ

  1. 可変長引数の挙動を理解する: ... を使ってスライスを渡すと、受け取った関数でも同じスライスを参照する。関数内で引数の渡し方を判別する確実な方法はない
  2. 常にコピーを作成: 可変長引数で受け取ったスライスを変更する場合は、必ず事前にコピーを作成するなど、動作が明確になるように設計を工夫する
  3. ドキュメント化: 関数の動作(副作用の有無)を明確にドキュメント化する

これらの原則に従うことで、予期しない副作用を避け、安全で保守性の高いGoコードを書くことができます。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?