Ada saatnya kita sungguh-sungguh tidak tahu tipe data yang akan lewat: argumen fungsi cetak serba-guna, hasil dekode JSON, atau isi kolom konfigurasi yang nilainya bisa angka, teks, maupun boolean. Go menyediakan satu tipe penampung untuk situasi seperti ini: empty interface, yaitu interface tanpa method sama sekali. Chapter ini membedah cara kerja empty interface, membuktikan bahwa tipe asli nilai tidak hilang di dalamnya, lalu membedah keterbatasan operasinya yang memaksa kita kembali ke tipe konkret sebelum nilai bisa dipakai.

Kontrak Tanpa Method #

Empty interface dideklarasikan sebagai interface{} — sepasang kurung kurawal kosong. Karena kontraknya tidak meminta method apa pun, setiap tipe di Go otomatis memenuhinya: method set kosong terkandung dalam method set tipe mana pun. Akibatnya variabel bertipe interface{} bisa menampung integer, string, slice, struct, pointer, bahkan interface lain tanpa konversi eksplisit.

go
var kotak interface{} = 42
kotak = "sekarang teks"
kotak = []string{"a", "b"}

Fragmen di atas sah karena setiap penugasan membentuk ulang pasangan (dynamic type, dynamic value) yang kita kenal di chapter Konsep Interface . Sebelum Go 1.18, interface{} adalah satu-satunya cara menulis tipe “apa saja”; sejak itu ada nama pendeknya, yaitu any, yang kita bahas pada chapter Tipe any .

Tipe Asli Tidak Hilang #

Nilai yang masuk ke empty interface kehilangan akses static ke method dan operasi aslinya, tetapi informasi tipenya sendiri tetap tersimpan utuh di dynamic type. Verb %T dari package fmt membuktikannya dengan mencetak tipe konkret yang sedang menempel. Inilah sebabnya fmt.Println sanggup mencetak tipe apa pun dengan format yang masuk akal: package fmt memeriksa isi interface saat runtime menggunakan reflection — mekanisme inspeksi tipe yang dijelaskan rinci di artikel The Laws of Reflection. Perilaku ini bukan sihir; fmt membaca dynamic type dari pasangan, mencari method String() atau Error(), lalu memilih format yang sesuai.

Operasi Konkret Ditolak Compiler #

Kemampuan menampung apa pun datang dengan harga: compiler hanya mengenal tipe static interface{}, sehingga operasi khas tipe konkret tidak bisa dilakukan langsung. Uji berikut dijalankan nyata pada Go 1.27:

text
invalid operation: x + 5 (mismatched types interface{} and untyped int)

Pesan tersebut muncul ketika var x interface{} = 10 dijumlahkan dengan 5. Compiler menolak karena penjumlahan tidak terdefinisi untuk tipe interface{} — meskipun dynamic value-nya integer. Perbandingan sederhana masih diizinkan (x == 42 pada demo di bawah sah, karena pembandingan antar interface mengikuti aturan khusus), namun aritmetika, pengindeksan, dan pemanggilan method milik tipe asal semuanya butuh langkah ekstra: mengeluarkan nilai kembali ke tipe konkret melalui type assertion yang kita bahas pada chapter Type Assertion .

Program Demo #

Berkas empty-demo.go mendemonstrasikan penampungan serba bisa dan sisa informasi tipe di dalamnya:

go
package main

import "fmt"

// tampilkan menerima nilai bertipe apa pun melalui interface kosong.
func tampilkan(v interface{}) {
	fmt.Printf("tipe: %T, nilai: %v\n", v, v)
}

func main() {
	tampilkan(42)
	tampilkan("belajar Go")
	tampilkan(3.14)
	tampilkan([]string{"io", "fmt", "os"})
	tampilkan(struct{ Kota string }{Kota: "Bandung"})

	// Tipe asli tidak hilang: %T tetap melaporkan tipe dinamisnya.
	var x interface{} = 42
	if x == 42 {
		fmt.Println("x masih bisa dibandingkan dengan 42")
	}
}

Output yang Dihasilkan:

text
tipe: int, nilai: 42
tipe: string, nilai: belajar Go
tipe: float64, nilai: 3.14
tipe: []string, nilai: [io fmt os]
tipe: struct { Kota string }, nilai: {Bandung}
x masih bisa dibandingkan dengan 42

Mari kita telaah baris demi baris program di atas:

  1. Baris 6–8 (func tampilkan(v interface{})): parameter bertipe empty interface membuat fungsi ini menerima argumen tipe apa pun tanpa konversi.
  2. Baris 7 (fmt.Printf): verb %T mencetak dynamic type dan %v mencetak dynamic value; keduanya dibaca lewat reflection di dalam package fmt.
  3. Baris 11–15 (lima pemanggilan tampilkan): integer, string, float, slice, dan struct anonim masuk ke parameter yang sama; setiap penugasan membentuk pasangan tipe-nilai baru.
  4. Baris 15 (struct{ Kota string }{...}): struct tanpa nama pun memenuhi interface kosong; %T mencetak bentuk lengkap tipe anonimnya.
  5. Baris 18 (var x interface{} = 42): nilai 42 disimpan sebagai dynamic type int; nilai literalnya tetap 42, bukan berubah menjadi teks.
  6. Baris 19–21 (if x == 42): pembandingan interface dengan nilai konkret diizinkan; perbandingannya membandingkan pasangan secara utuh, bukan sekadar tipe static.

Empty interface berfungsi sebagai amplop netral untuk data transit: tipe aslinya tercatat, tetapi isinya harus dibuka kembali sebelum dipakai. Karena menulis interface{} berkali-kali terasa berat, Go 1.18 memperkenalkan alias resmi untuknya — any — yang menjadi fokus chapter berikutnya.

Referensi #

Berkas Sumber

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

empty-demo.go

Unduh berkas
go
package main

import "fmt"

// tampilkan menerima nilai bertipe apa pun melalui interface kosong.
func tampilkan(v interface{}) {
	fmt.Printf("tipe: %T, nilai: %v\n", v, v)
}

func main() {
	tampilkan(42)
	tampilkan("belajar Go")
	tampilkan(3.14)
	tampilkan([]string{"io", "fmt", "os"})
	tampilkan(struct{ Kota string }{Kota: "Bandung"})

	// Tipe asli tidak hilang: %T tetap melaporkan tipe dinamisnya.
	var x interface{} = 42
	if x == 42 {
		fmt.Println("x masih bisa dibandingkan dengan 42")
	}
}