Interface kosong menampung nilai apa pun karena tidak menuntut satu metode pun. Topik ini membahas deklarasi any, kondisi yang membuat pengetikan dinamis beralasan, serta perbandingannya dengan generics yang memeriksa tipe saat kompilasi. Setelah membacanya, pemilihan antara any, interface kecil, dan parameter tipe dapat dipertimbangkan dengan alasan yang jelas dan bukan sekadar kebiasaan.
Deklarasi any#
Interface kosong (empty interface) adalah interface tanpa metode, sehingga setiap tipe memenuhinya. Deklarasinya memang tidak memiliki isi, dan pustaka standar menyediakannya dengan nama alias any.
// tidak-dijalankan
type any = interface{}Alias tersebut diperkenalkan sejak Go 1.18 untuk menuliskan interface{} dengan lebih ringkas, dan keduanya menunjuk tipe yang sama. Variabel any dapat diberi nilai apa pun, tetapi informasi tipe konkretnya tidak tercantum pada tipe statisnya. Program berikut mengisi satu variabel yang sama dengan tiga nilai yang berbeda.
package main
import "fmt"
func main() {
var data any
data = "arsip catatan"
fmt.Println(data)
data = 128
fmt.Println(data)
data = []string{"draf", "final"}
fmt.Println(data)
fmt.Printf("tipe dinamisnya %T\n", data)
}arsip catatan
128
[draf final]
tipe dinamisnya []stringSetiap penugasan berhasil karena semua tipe memenuhi any. Sebaliknya, nilai tersebut tidak dapat langsung dipakai sebagai string atau int, karena operasi yang menuntut tipe konkret ditolak saat kompilasi. Tipe dinamisnya dibaca kembali melalui verb %T sebagaimana terlihat pada baris terakhir, dan pemakaiannya kembali memerlukan type assertion atau type switch. Program tersebut dapat dijalankan lewat terminal sebagaimana berikut.
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gointerface/interface-kosong
cp nilai-apa-pun.go /tmp/gointerface/interface-kosong/
cd /tmp/gointerface/interface-kosong
# periksa format sebelum menjalankan programnya
gofmt -l .
# jalankan programnya
go run nilai-apa-pun.goarsip catatan
128
[draf final]
tipe dinamisnya []stringKeluarannya sama dengan blok sebelumnya karena keduanya menjalankan program yang sama, sedangkan gofmt -l tidak mencetak apa pun ketika format kode sudah sesuai.
Kapan pengetikan dinamis beralasan#
Pengetikan dinamis (dynamic typing), yaitu penentuan tipe yang dilakukan saat program berjalan, beralasan pada tiga kondisi. Pertama, pada batas data yang strukturnya ditentukan di luar program, seperti dokumen JSON dan data dari layanan lain. Kedua, pada fungsi yang memang mengelola nilai sembarang, seperti fmt.Println yang parameternya bertipe ...any. Ketiga, pada kontainer yang isinya memang bercampur tipe dan jarang dipakai. Di luar ketiganya, tipe konkret atau interface kecil biasanya memberi jaminan yang lebih kuat.
Paket encoding/json memakai map[string]any untuk data yang strukturnya belum diketahui, dan setiap bilangan pada dokumen menjadi float64. Program berikut membaca dokumen JSON kecil lalu memeriksa tipe tiap nilainya.
package main
import (
"encoding/json"
"fmt"
)
func main() {
teks := []byte(`{"judul":"log sensor","banyak":24,"final":true}`)
var data map[string]any
if err := json.Unmarshal(teks, &data); err != nil {
fmt.Println("error:", err)
return
}
// kunci diakses lewat daftar tetap agar urutannya pasti
for _, kunci := range []string{"judul", "banyak", "final"} {
fmt.Printf("%s: %v (tipe %T)\n", kunci, data[kunci], data[kunci])
}
}judul: log sensor (tipe string)
banyak: 24 (tipe float64)
final: true (tipe bool)Field banyak yang tertulis 24 terbaca sebagai float64, karena format JSON tidak membedakan bilangan bulat dan bilangan desimal. Pengaksesan kunci dilakukan lewat daftar tetap agar urutan cetakannya pasti, sebab urutan perulangan peta memang tidak dijamin. Untuk data yang strukturnya sudah stabil, deklarasi struct dengan tag JSON menghindarkan kode dari asersi tipe sama sekali.
any dan generics#
Generics (generics), yaitu parameter tipe yang diperiksa saat kompilasi, menyelesaikan persoalan yang berbeda. Fungsi generik menerima satu jenis tipe pada tiap pemanggilannya dan tetap mengenali tipe itu di dalam badan fungsinya, sehingga tidak ada asersi. Program berikut menempatkan keduanya berdampingan pada kasus penjumlahan.
package main
import "fmt"
// jumlahAny menerima interface kosong sehingga pemanggilnya dapat mengirim
// nilai apa pun, tetapi setiap pemakaian menuntut asersi tipe.
func jumlahAny(a, b any) (int, error) {
x, ok := a.(int)
if !ok {
return 0, fmt.Errorf("argumen pertama bukan int, melainkan %T", a)
}
y, ok := b.(int)
if !ok {
return 0, fmt.Errorf("argumen kedua bukan int, melainkan %T", b)
}
return x + y, nil
}
// jumlahT adalah fungsi generik: kesalahan tipe terdeteksi saat kompilasi.
func jumlahT[T int | float64](a, b T) T {
return a + b
}
func main() {
jumlah, err := jumlahAny(10, 24)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("jumlah:", jumlah)
if _, err := jumlahAny(10, "banyak"); err != nil {
fmt.Println("error:", err)
}
fmt.Println(jumlahT(10, 24))
fmt.Println(jumlahT(1.5, 2.5))
}jumlah: 34
error: argumen kedua bukan int, melainkan string
34
4Fungsi jumlahAny harus berasersi dua kali dan melaporkan penolakan lewat error saat program berjalan, sedangkan jumlahT menolak pemanggilan yang tipenya tidak cocok lebih dahulu saat kompilasi. Tabel berikut merangkum perbedaan keduanya.
| Aspek | any | Generics |
|---|---|---|
| Pemeriksaan tipe | Saat program berjalan, melalui asersi | Saat kompilasi |
| Nilai yang diterima | Sembarang tipe, boleh bercampur | Satu tipe per instansiasi fungsi |
| Biaya penulisan | Asersi tipe di setiap pemakaian | Deklarasi parameter tipe |
| Pemakaian yang cocok | Data dinamis seperti JSON dan fungsi pencetak | Operasi seragam atas satu tipe |
Panduan resmi Go dalam artikel When To Use Generics menempatkan generics sebagai alat untuk kode yang logikanya identik antar tipe dan bukan sebagai pengganti interface. Interface kecil tetap menjadi alat utama abstraksi perilaku, sedangkan any menyatakan bahwa pengetahuan tipe sengaja ditunda.
Kesalahan umum#
Tiga kebiasaan berikut membuat kode yang memakai any menjadi rapuh.
- Menganggap
anybebas aturan tipe. Operasi sepertidata + 1pada variabelanyditolak kompilator dengan pesaninvalid operation: data + 1 (mismatched types any and untyped int). Perbaikannya, berasersi lebih dahulu ke tipe konkret, atau gunakan parameter tipe bila operasinya sama untuk banyak tipe. - Mengandalkan urutan perulangan peta saat memproses
map[string]any. Urutan iterasi peta diacak pada setiap eksekusi, sehingga keluaran yang tercetak berubah-ubah dan perbandingan antar pengujian tidak dapat dipercaya. Perbaikannya, akses kunci lewat daftar tetap atau urutkan kuncinya lebih dahulu. - Memilih
anydemi menghindari penulisan tipe. Asersi pun bermunculan di seluruh kode, dan kesalahan tipe baru terlihat saat program berjalan. Perbaikannya, kembalikan tipe konkret, interface kecil, atau parameter tipe sesuai kebutuhannya.
Ringkasan#
anyadalah alias untuk interface kosong, dan setiap nilai memenuhinya karena tidak ada metode yang dituntut.- Informasi tipe konkret hilang dari tipe statis
any, sehingga pemakaiannya kembali menuntut type assertion atau type switch. - Pengetikan dinamis beralasan pada batas data eksternal, fungsi yang mengelola nilai sembarang, dan kontainer yang isinya bercampur.
- Generics memeriksa tipe saat kompilasi dan lebih cocok untuk logika yang identik antar tipe.
Referensi#
- Package builtin — The Go Authors
— Definisi alias
anypada pustaka standar. - When To Use Generics — The Go Blog — Panduan resmi kapan generics dipilih dan kapan tidak.
- The Go Programming Language Specification — The Go Project — Aturan resmi interface dan alias tipe.