ProgrammingGoプログラマ

Goにおける組み込み関数append、len、capとは何か、言語のレベルでどのように機能するのか、そしてそれらの使用に関連するスライスの隠れた危険性は何か?

Hintsage AIアシスタントで面接を突破

回答

Goでは、組み込み関数appendlen、およびcapはスライス(slices)を操作する際に重要な役割を果たします。

  • **len(slice)**はスライス内の要素の数を返します。
  • **cap(slice)**はスライスの容量を返します―新しいメモリを割り当てることなく最大で保持できる要素の数を示します。
  • **append(slice, elems ...T)**は十分な容量がない場合、新しい配列(およびそれに伴って新しいバックストレージ)を作成するため、スライスの引き渡しのセマンティクスを予期せず変更する可能性があります。

例:

arr := []int{1,2,3} arr2 := append(arr, 4) // arr2はcap(arr)に依存して、同じまたは新しいバックアレイになる可能性があります。

注意点:

  • 他のスライス(または元の配列)から「切り取った」スライスにappendを行うと、サイドエフェクトが発生する可能性があります:元のデータが変更されることです。
  • スライスを関数に渡すと、len/capは「見える」部分にのみ作用します。
  • capを超えるappendを行うと、新しい配列が作成され、古い配列はそのままです。

トリック質問

スライスから「切り取った」部分に要素を追加してappendした場合、元の配列に影響を与えますか?

回答:capが許可する場合、appendは元の配列の「尾」に要素を追加し、元の配列を指すすべてのスライスで変更が見えます。

例:

a := []int{1,2,3,4} b := a[:2] // [1 2], len=2, cap=4 b = append(b, 10) // aが変更される: a -> [1, 2, 10, 4]

実際のエラーの例


逸話

チームが子スライスに要素を追加したところ、親配列のデータが予期せず変更され、ビジネスロジックの不整合が生じ、ユーザー間のタスク配分においてデバッグが難しいバグを引き起こしました。


逸話

大きなスライスに対してappendを再度呼び出す際、常に新しい配列が再割り当てされると期待していましたが、実際にはシステムのいくつかの部分が同じ「バックアレイ」で動作し続け、レースコンディションやデータの損傷を引き起こしていました。


逸話

開発者はmakeを使って固定サイズのスライスを割り当てましたが、誤って引数を入れ替えました:make([]int, cap, len)。その結果、容量に基づくロジックが不意に長さに対して動作し、s[0:len]の範囲を超えるとパニックを引き起こしました。