Go slices.Concat: รวมหลาย slice โดยไม่ผูกผลลัพธ์กับ backing array เดิม
ถ้าต้องรวม slice หลายก้อน โค้ดแบบ append(append(a, b...), c...) ดูสั้น แต่ซ่อนคำถามสำคัญไว้ครับ: ผลลัพธ์กำลังใช้ backing array ของ a ต่อหรือไม่ และการแก้ผลลัพธ์ภายหลังจะย้อนกลับไปแตะ input หรือเปล่า slices.Concat สื่อเจตนา “สร้างชุดรวม” ตรงกว่า และคืน slice ใหม่ที่ไม่แชร์ backing array กับ input เมื่อผลไม่ว่าง
WHY: แยก ownership ให้คนอ่านเห็น
นึกภาพใบสั่งอาหารสามโต๊ะครับ การเอากระดาษโต๊ะแรกมาเย็บต่ออาจเร็ว แต่คนโต๊ะแรกยังถือสำเนาเดิมอยู่ ถ้ามีใครแก้บรรทัดบนกระดาษร่วมกัน ความหมายจะสับสน Concat เหมือนคัดรายการทั้งหมดลงแผ่นใหม่ จึงเหมาะเมื่อขอบเขต ownership สำคัญกว่าการบีบ allocation ทุกไบต์
API คำนวณความยาวรวมก่อน ถ้าผลรวมล้น int จะ panic แทนการได้ slice ที่ความยาวเพี้ยน ส่วนลำดับข้อมูลคงตามลำดับ argument และลำดับภายในแต่ละ slice
HOW: รวมแล้วพิสูจน์ว่า input ไม่ถูกแก้
package concat_test
import (
"slices"
"testing"
)
func TestConcatOwnsResult(t *testing.T) {
left := []string{"api", "worker"}
middle := []string{"cache"}
right := []string{"queue", "db"}
got := slices.Concat(left, middle, right)
want := []string{"api", "worker", "cache", "queue", "db"}
if !slices.Equal(got, want) {
t.Fatalf("got %v want %v", got, want)
}
got[0] = "changed"
if left[0] != "api" {
t.Fatalf("result aliases input: left=%v", left)
}
}
การทดสอบนี้จงใจแก้ element แรกของผล ถ้า input เปลี่ยนตาม แปลว่า ownership ยังพันกัน แต่ Concat ทำให้ assertion ผ่านบน Go ที่ตรวจในรอบนี้
เลือกแบบไหน
| สถานการณ์ | ทางเลือกที่เหมาะ |
|---|---|
| ต้องการผลใหม่ อ่านง่าย และไม่แชร์ storage กับ input | slices.Concat |
| มี destination buffer ที่เป็นเจ้าของเองและจอง capacity ไว้แล้ว | append(dst, src...) |
| รับข้อมูลเป็น stream ยาวโดยไม่รู้ขนาดรวม | append ทีละช่วง พร้อมวัด allocation |
| ต้องต่อข้อมูลโดยไม่ materialize ทั้งหมด | ใช้ iterator/reader ตามชนิดข้อมูล |
Concat ไม่ได้แปลว่าเร็วที่สุดทุก workload ถ้าทีมมี buffer pool หรือ destination ที่จองไว้ การ append ลง buffer นั้นอาจลด allocation ได้ แต่ต้องเขียน contract ownership ให้ชัด อย่าแลกบรรทัดสั้นกับ bug ที่โผล่ตอนคนละ goroutine ถือ slice คนละชื่อแต่ใช้ array เดียวกัน
Recap
- ใช้
Concatเมื่อความหมายคือสร้างชุดรวมใหม่ - จำว่าลำดับ input และ element ถูกเก็บไว้
- ทดสอบ aliasing หาก caller จะ mutate ผลลัพธ์
- ใช้ buffer เดิมเฉพาะเมื่อ ownership และ capacity ชัดเจน