Constraint seperti ~int | ~float64 terlihat seperti daftar belanja tipe, tetapi di baliknya ada model konseptual yang lebih dalam: sejak Go 1.18, setiap interface mendefinisikan type set — himpunan tipe konkret yang memenuhi interface tersebut. Model ini menjelaskan mengapa operator tertentu sah pada type parameter, mengapa irisan interface bisa mempersempit himpunan, dan mengapa union int | string hanya mengizinkan operasi yang berlaku untuk keduanya. Chapter ini membedah type sets lapis demi lapis dan membuktikan setiap aturan lewat compiler yang asli.
Interface Mendefinisikan Dua Hal #
Sebelum generics, interface didefinisikan sebagai method set: kumpulan method yang harus dimiliki suatu tipe. Go 1.18 memperluas definisi tersebut menjadi dua bagian:
type Berlabel interface {
String() string
}Interface Berlabel tetap punya method set String() string, dan type set-nya mencakup semua tipe yang mengimplementasikan method tersebut. Interface constraint menambahkan jenis elemen baru — union tipe — yang membangun type set secara langsung tanpa method:
type Bilangan interface {
~int | ~float64
}Type set Bilangan adalah himpunan semua tipe dengan underlying type int atau float64. Sebuah tipe memenuhi constraint jika tipe tersebut termasuk anggota type set constraint, dan sebuah operasi pada type parameter sah jika operasi itu valid untuk setiap anggota himpunan.
Elemen Union dan Elemen Tilde #
Union menyusun himpunan dengan operator “atau”: ~int | ~float64 berarti anggota himpunan adalah gabungan semua tipe ber-underlying int dan semua tipe ber-underlying float64. Elemen tilde ~T menyeleksi berdasarkan underlying type, sehingga tipe buatan seperti type Skor int masuk himpunan ~int. Elemen tanpa tilde seperti int pada int | string menyeleksi tipe persis — hanya int identitas yang lolos, bukan turunannya.
Irisan: Menyempitkan Himpunan dengan Embedding #
Interface bisa meng-embed interface lain, dan ketika constraint meng-embed lebih dari satu interface, type set yang terbentuk adalah irisan himpunan-himpunan tersebut:
type BulatBerlabel interface {
Berlabel
~int
}Constraint BulatBerlabel hanya menerima tipe yang sekaligus punya method String dan ber-underlying int — kombinasi yang sulit dicapai satu union, karena union hanya menambah anggota sedangkan irisan memangkas. Tipe Skor pada program demo adalah anggota tepat irisan ini.
Operasi Sah Ditentukan oleh Seluruh Himpunan #
Aturan inti type sets: operator pada type parameter sah hanya jika valid untuk semua anggota himpunan. Union int | string adalah contoh pembelajaran yang menarik: operator + berlaku untuk int dan string sehingga boleh dipakai, sedangkan * hanya berlaku untuk int — satu anggota yang tidak mendukung sudah cukup menolak seluruh operasi, seperti dibuktikan berkas error-operator.go:
package main
import "fmt"
// Tempel memakai union int | string tanpa tilde.
func Tempel[T int | string](a, b T) T {
// Operator * tidak sah untuk string sehingga ditolak compiler,
// walaupun T bisa berupa int.
return a * b
}
func main() {
fmt.Println(Tempel(10, 32))
}Output yang Dihasilkan:
# command-line-arguments
./error-operator.go:9:9: invalid operation: operator * not defined on a (variable of type T constrained by int | string)Pesan tersebut menyebut constraint secara eksplisit: T constrained by int | string. Union semacam ini jarang bernilai — constraint berguna ketika himpunannya mendukung operasi yang dibutuhkan isi fungsi.
Interface Biasa vs Interface Constraint #
Type set tidak membuat semua interface menjadi constraint. Interface yang elemennya hanya method — seperti Berlabel — tetap berperilaku sebagai interface biasa: boleh menjadi tipe variabel, parameter, dan nilai balik. Sebaliknya, interface yang memuat union atau tilde hanya boleh dipakai sebagai constraint: deklarasi var x interface{ ~int } ditolak compiler dengan pesan cannot use type interface{~int} outside a type constraint: interface contains type constraints.
Program Demo #
Berkas typesets-demo.go menggabungkan union, tilde, dan irisan dalam satu program:
package main
import "fmt"
// Berlabel: interface biasa yang hanya berisi method set.
type Berlabel interface {
String() string
}
// Bilangan: constraint dengan union dua elemen tilde.
type Bilangan interface {
~int | ~float64
}
// Skor: tipe dengan underlying int sekaligus method String.
type Skor int
func (s Skor) String() string {
return fmt.Sprintf("skor %d", int(s))
}
// BulatBerlabel adalah irisan: underlying int DAN memiliki method String.
type BulatBerlabel interface {
Berlabel
~int
}
// Operator aritmetika sah karena seluruh anggota himpunan ber-underlying int.
func Naikkan[L BulatBerlabel](nilai L) L {
return nilai + 1
}
// Operator tambah sah untuk seluruh anggota himpunan Bilangan.
func Gabung[B Bilangan](a, b B) B {
return a + b
}
// Tempel memakai union int | string tanpa tilde.
// Operator + sah karena berlaku untuk int maupun string.
func Tempel[T int | string](a, b T) T {
return a + b
}
func main() {
var ujian Skor = 87
naik := Naikkan(ujian)
fmt.Println(naik.String())
fmt.Println(Gabung(10, 32))
fmt.Println(Gabung(1.5, 2.25))
fmt.Println(Tempel(10, 32))
fmt.Println(Tempel("go", "lang"))
}Output yang Dihasilkan:
skor 88
42
3.75
42
golangMari kita telaah baris demi baris program di atas:
- Baris 6-8 (
type Berlabel): interface method-only; type set-nya mencakup semua tipe punyaString() string, termasukSkor. - Baris 11-13 (
type Bilangan): constraint union dengan dua elemen tilde; type set-nya gabungan tipe ber-underlyingintdanfloat64. - Baris 16-20 (
type Skordan method): tipe bernama ber-underlyingintyang juga mengimplementasikanString— anggota dariBilangansekaligusBulatBerlabel. - Baris 23-26 (
type BulatBerlabel): irisan dua interface; baris 24 meng-embedBerlabeldan baris 25 menambah elemen~int, sehingga hanya tipe ber-underlyingintpunyaString()yang lolos. - Baris 29-31 (
Naikkan): operator+pada baris 30 sah karena semua anggotaBulatBerlabelber-underlyingint; nilai balik tetap bertipeLsehinggaSkormasuk keluar tanpa konversi. - Baris 46-47 (
Naikkan(ujian)):Ttersimpulkan sebagaiSkor; hasil87 + 1bertipeSkordan methodStringmencetakskor 88. - Baris 52-53 (
Tempel): satu fungsi yang sama menjumlahkanintdan menggabungkanstringkarena+valid untuk semua anggota himpunanint | string.
Referensi #
- Spesifikasi Go: Type sets — definisi resmi type set, union, dan irisan.
- Spesifikasi Go: Type constraints — aturan pemakaian constraint dan operasi sahnya.
Model type sets kini menjadi fondasi untuk praktik: menyusun fungsi generik produksi seperti Map, Filter, dan Reduce. Chapter Generic Function
menyusun ketiganya dengan type parameter ganda dan membuktikan kualitasnya lewat inferensi tipe.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
error-operator.go
Unduh berkaspackage main
import "fmt"
// Tempel memakai union int | string tanpa tilde.
func Tempel[T int | string](a, b T) T {
// Operator * tidak sah untuk string sehingga ditolak compiler,
// walaupun T bisa berupa int.
return a * b
}
func main() {
fmt.Println(Tempel(10, 32))
}
typesets-demo.go
Unduh berkaspackage main
import "fmt"
// Berlabel: interface biasa yang hanya berisi method set.
type Berlabel interface {
String() string
}
// Bilangan: constraint dengan union dua elemen tilde.
type Bilangan interface {
~int | ~float64
}
// Skor: tipe dengan underlying int sekaligus method String.
type Skor int
func (s Skor) String() string {
return fmt.Sprintf("skor %d", int(s))
}
// BulatBerlabel adalah irisan: underlying int DAN memiliki method String.
type BulatBerlabel interface {
Berlabel
~int
}
// Operator aritmetika sah karena seluruh anggota himpunan ber-underlying int.
func Naikkan[L BulatBerlabel](nilai L) L {
return nilai + 1
}
// Operator tambah sah untuk seluruh anggota himpunan Bilangan.
func Gabung[B Bilangan](a, b B) B {
return a + b
}
// Tempel memakai union int | string tanpa tilde.
// Operator + sah karena berlaku untuk int maupun string.
func Tempel[T int | string](a, b T) T {
return a + b
}
func main() {
var ujian Skor = 87
naik := Naikkan(ujian)
fmt.Println(naik.String())
fmt.Println(Gabung(10, 32))
fmt.Println(Gabung(1.5, 2.25))
fmt.Println(Tempel(10, 32))
fmt.Println(Tempel("go", "lang"))
}