Fungsi yang menerima channel sebagai parameter biasanya hanya membutuhkan separuh kemampuan channel tersebut: produsen hanya mengirim, konsumen hanya menerima. Ketika parameter bertipe chan int polos, kedua fungsi itu sama-sama bisa mengirim dan menerima, sehingga niat program tidak terbaca dari signature dan kesalahan peran hanya ketahuan saat runtime. Go mengatasinya lewat channel direction: tipe chan<- T dan <-chan T mengunci channel menjadi kirim-saja atau terima-saja, dan compiler menolak pelanggaran arah sebelum program sempat berjalan. Chapter ini mempraktikkan kedua arah, lalu menyaksikan pesan error compiler saat arah dilanggar.

Tipe Arah: chan<- dan <-chan #

Arah dituliskan dengan posisi tanda panah terhadap kata chan. Panah yang mengarah ke kanan menuju chan (chan<-) berarti data mengalir masuk ke channel: kirim-saja. Panah yang menjauh dari chan ke kiri (<-chan) berarti data mengalir keluar: terima-saja:

go
func produksi(ch chan<- int) // hanya bisa: ch <- v
func konsumsi(ch <-chan int) // hanya bisa: v := <-ch

Channel yang dibuat dengan make selalu dua arah (bidirectional). Konversi ke tipe searah terjadi otomatis saat channel dua arah diberikan sebagai argumen — pemanggil meneruskan ch biasa, dan parameter menerima versi yang dikunci arahnya. Konversi balik dari searah menjadi dua arah tidak diizinkan; begitu arah dikunci, kunci itu permanen di dalam fungsi.

Direction sebagai Kontrak Fungsi #

Berkas channel-direction-demo.go menyusun pola produsen-konsumen lengkap dengan arah terkunci di kedua parameter:

go
package main

import "fmt"

// produksi hanya boleh mengirim nilai ke ch.
func produksi(ch chan<- int, data []int) {
	for _, v := range data {
		ch <- v
	}
	close(ch)
}

// konsumsi hanya boleh menerima nilai dari ch.
func konsumsi(ch <-chan int) []int {
	hasil := make([]int, 0, 4)
	for v := range ch {
		hasil = append(hasil, v)
	}
	return hasil
}

func main() {
	ch := make(chan int)

	go produksi(ch, []int{10, 20, 30, 40})
	hasil := konsumsi(ch)

	total := 0
	for _, v := range hasil {
		total += v
	}
	fmt.Println("diterima:", hasil)
	fmt.Println("total   :", total)
}

Output yang Dihasilkan:

text
diterima: [10 20 30 40]
total   : 100

Mari kita telaah baris demi baris program di atas:

  1. Baris 6 (ch chan<- int): parameter produksi dikunci kirim-saja; di dalam fungsi, statement terima <-ch akan gagal kompilasi, sehingga peran produsen terjaga oleh compiler.
  2. Baris 10 (close(ch)): menutup channel masih sah dari tipe kirim-saja karena close adalah wewenang pengirim — konsisten dengan kaidah “hanya pengirim yang boleh menutup”.
  3. Baris 14 (ch <-chan int): parameter konsumsi dikunci terima-saja; fungsi ini tidak mungkin mengirim data palsu ke channel produsen.
  4. Baris 16–18 (for v := range ch): konsumsi menerima nilai sampai channel ditutup, mengumpulkan salinan tiap nilai ke slice hasil.
  5. Baris 23–26: main membuat satu channel dua arah lalu membagikannya — menjadi kirim-saja bagi produksi dan terima-saja bagi konsumsi melalui konversi otomatis.
  6. Baris 32–33: hasil dicetak setelah konsumsi kembali, sehingga urutan nilai (dan totalnya) pasti sesuai urutan pengiriman.

Dua detail pattern ini layak diingat. Pertama, signature menjadi dokumentasi: pembaca kode tahu tanpa membuka isi fungsi bahwa produksi adalah sumber data dan konsumsi adalah muara. Kedua, channel mengirim tiap nilai kepada tepat satu penerima — jika dua konsumen membaca channel yang sama, nilai terbagi di antara mereka (bukan disalin ke semua), karena itu pola ini juga menjadi dasar distribusi beban yang dibahas di chapter Worker Pool .

Pelanggaran Arah Ditolak Saat Kompilasi #

Kekuatan sesungguhnya dari direction adalah waktu penangkapan kesalahan: sebelum program berjalan. Berkas error-direction.go sengaja berisi fungsi berkontrak kirim-saja yang mencoba menerima jawaban:

go
package main

import "fmt"

// laporan berkontrak hanya mengirim hasil lewat ch.
func laporan(ch chan<- string) {
	ch <- "data siap"
	balasan := <-ch // salah: menerima dari channel kirim-saja
	fmt.Println("balasan:", balasan)
}

func main() {
	ch := make(chan string)
	go laporan(ch)
	fmt.Println(<-ch)
}

Jalankan berkas tersebut:

bash
go run error-direction.go

Output yang Dihasilkan:

text
# command-line-arguments
./error-direction.go:8:15: invalid operation: cannot receive from send-only channel chan<- string ch (variable of type chan<- string)

Program gagal dikompilasi dan go run berakhir dengan exit code 1, jadi tidak ada output runtime sama sekali. Lokasi 8:15 menunjuk tepat operasi terima <-ch pada baris kedelapan. Pesan ini adalah umpan balik paling murah yang bisa diberikan sistem: kesalahan arah tertangkap di meja compiler, bukan menjadi deadlock misterius di server produksi. Perbaikannya bukan mengubah tipe parameter, melainkan merancang ulang alur — misalnya memakai channel terima-saja terpisah bila jawaban memang diperlukan.

Referensi #

Arah channel menjawab pertanyaan “siapa boleh apa”. Pertanyaan berikutnya dalam program concurrent lebih dinamis: “operasi mana yang siap lebih dulu” — dan untuk itu Go menyediakan statement select pada chapter selanjutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

channel-direction-demo.go

Unduh berkas
go
package main

import "fmt"

// produksi hanya boleh mengirim nilai ke ch.
func produksi(ch chan<- int, data []int) {
	for _, v := range data {
		ch <- v
	}
	close(ch)
}

// konsumsi hanya boleh menerima nilai dari ch.
func konsumsi(ch <-chan int) []int {
	hasil := make([]int, 0, 4)
	for v := range ch {
		hasil = append(hasil, v)
	}
	return hasil
}

func main() {
	ch := make(chan int)

	go produksi(ch, []int{10, 20, 30, 40})
	hasil := konsumsi(ch)

	total := 0
	for _, v := range hasil {
		total += v
	}
	fmt.Println("diterima:", hasil)
	fmt.Println("total   :", total)
}

error-direction.go

Unduh berkas
go
package main

import "fmt"

// laporan berkontrak hanya mengirim hasil lewat ch.
func laporan(ch chan<- string) {
	ch <- "data siap"
	balasan := <-ch // salah: menerima dari channel kirim-saja
	fmt.Println("balasan:", balasan)
}

func main() {
	ch := make(chan string)
	go laporan(ch)
	fmt.Println(<-ch)
}