กระรอกดินต้นฉบับกำลังต่อถาดข้อมูลสามใบเข้ารางใหม่บนโต๊ะสีมิ้นต์ โดยถาดต้นทางยังวางแยกอยู่

Go slices.Concat: รวมหลาย slice โดยไม่ผูกผลลัพธ์กับ backing array เดิม

พี่โชว์Engineering

ถ้าต้องรวม 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 กับ inputslices.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 ชัดเจน