Pada sebagian besar bahasa berorientasi objek, sebuah class harus mendaftarkan diri dengan menulis implements NamaInterface agar diakui sebagai implementasi. Go membuang syarat tersebut: tipe apa pun yang memiliki method dengan signature yang cocok otomatis memenuhi interface, tanpa satu baris deklarasi hubungan pun. Aturan ini membuat kode terasa ringan, tetapi juga menimbulkan dua pertanyaan praktis: bagaimana kita memastikan sebuah tipe benar-benar memenuhi kontrak, dan apa bedanya dengan duck typing di bahasa dinamis? Chapter ini menjawab keduanya, ditutup dengan idiom pemeriksaan saat kompilasi yang wajib Anda kenal.

Tipe Memenuhi Interface Secara Otomatis #

Aturan resminya sederhana: sebuah tipe T mengimplementasikan interface I jika method set milik T memuat semua method yang dideklarasikan di I. Tidak ada pendaftaran, tidak ada annotation, dan tidak ada kata kunci tambahan pada deklarasi struct. Pemeriksaan dilakukan compiler tepat di titik nilai T dipakai sebagai I, misalnya saat assignment variabel, argumen pemanggilan fungsi, atau elemen slice bertipe interface.

Konsekuensi arsitekturalnya penting: paket yang mendefinisikan Pelanggan tidak perlu mengimpor paket tempat PemberiSalam dideklarasikan. Hubungan tipe-interface hanya bersifat satu arah — dari sisi pemakai, bukan dari sisi implementasi. Standar library memanfaatkan ini secara ekstrem: fmt tidak tahu apa pun tentang struct Anda, tetapi struct yang mengimplementasikan String() langsung tampil lebih rapi di output fmt tanpa satu baris integrasi.

Duck Typing yang Diperiksa Saat Kompilasi #

Gaya ini sering disamakan dengan duck typing di Python atau Ruby: “kalau berjalan seperti bebek dan bersuara seperti bebek, berarti bebek”. Kesamaannya hanya pada absennya deklarasi eksplisit. Perbedaan fundamentalnya: Go memeriksa kecocokan method sebelum program berjalan, saat kompilasi, bukan saat pemanggilan di runtime.

Buktinya mudah dibuat. Jika kita menghapus method Salam milik Pelanggan lalu tetap menugaskannya ke PemberiSalam, kompilasi langsung berhenti dengan pesan berikut:

text
cannot use Pelanggan{} (value of struct type Pelanggan) as PemberiSalam value in variable declaration: Pelanggan does not implement PemberiSalam (missing method Salam)

Pesan tersebut dihasilkan compiler Go 1.27 dari uji nyata, bukan tebakan dokumentasi lama. Di bahasa duck typing murni, kesalahan sejenis baru muncul sebagai exception saat baris bermasalah dieksekusi. Dengan demikian, istilah yang lebih tepat untuk Go adalah implicit interface satisfaction dengan static check.

Idiom var _ Interface = Tipe #

Karena hubungan tidak dideklarasikan, kita perlu cara eksplisit untuk mengunci kontrak. Idiom standarnya adalah assignment ke blank identifier di level package:

go
var _ PemberiSalam = Pelanggan{}
var _ PemberiSalam = Kurir{}

Baris tersebut tidak mengalokasikan apa pun dan tidak bisa diakses runtime — blank identifier _ membuang nilai. Fungsinya murni sebagai jaminan kompilasi: jika kelak seseorang mengganti signature Salam atau menghapusnya, build gagal di file ini dengan pesan missing method seperti di atas. Letakkan idiom ini di paket yang mendefinisikan tipe, tepat setelah deklarasinya, agar kontrak terlihat dan terjaga tanpa menambah dependensi impor.

Program Demo #

Berkas implicit-demo.go menyatukan seluruh mekanisme di atas. Jalankan dari direktori chapter:

go
package main

import "fmt"

// PemberiSalam hanya mendefinisikan kontrak, bukan implementasi.
type PemberiSalam interface {
	Salam() string
}

// Pelanggan dan Kurir tidak menyebut PemberiSalam pada deklarasinya.
type Pelanggan struct {
	Nama string
}

func (p Pelanggan) Salam() string {
	return "Halo, " + p.Nama + "!"
}

type Kurir struct {
	Kode string
}

func (k Kurir) Salam() string {
	return "Paket milik kurir " + k.Kode + " siap diambil."
}

// Cek kompilasi: kedua tipe wajib memenuhi PemberiSalam, jika tidak build gagal.
var _ PemberiSalam = Pelanggan{}
var _ PemberiSalam = Kurir{}

// sapaSemua bekerja untuk semua tipe yang punya method Salam.
func sapaSemua(daftar []PemberiSalam) {
	for _, s := range daftar {
		fmt.Println(s.Salam())
	}
}

func main() {
	daftar := []PemberiSalam{
		Pelanggan{Nama: "Budi"},
		Kurir{Kode: "K-07"},
	}
	sapaSemua(daftar)
}

Output yang Dihasilkan:

text
Halo, Budi!
Paket milik kurir K-07 siap diambil.

Mari kita telaah baris demi baris program di atas:

  1. Baris 6–8 (type PemberiSalam interface): mendeklarasikan kontrak satu method Salam() string yang menjadi acuan seluruh program.
  2. Baris 15–17 (func (p Pelanggan) Salam): method Salam untuk Pelanggan; deklarasi ini — dan hanya ini — membuat Pelanggan memenuhi interface, tanpa menyebut PemberiSalam.
  3. Baris 23–25 (func (k Kurir) Salam): Kurir memenuhi kontrak yang sama dengan signature identik; nama field dan isi method boleh berbeda total.
  4. Baris 27–29 (var _ PemberiSalam = ...): idiom jaminan kompilasi; kedua tipe dipaksa lulus pemeriksaan method set setiap kali program di-build.
  5. Baris 32–36 (func sapaSemua): fungsi ini menerima []PemberiSalam, satu slice yang anggotanya boleh berasal dari tipe konkret berbeda-beda.
  6. Baris 39–43 (daftar := ...): slice literal mengisi dua elemen dari dua tipe berbeda; assignment tiap elemen memicu pemeriksaan implisit saat kompilasi.

Implementasi implisit menjadikan interface sebagai alat pemakai, bukan alat pendaftaran. Namun kontrak yang bisa menerima tipe apa pun juga membuka pintu lain: bagaimana jika kita ingin satu variabel benar-benar boleh menampung nilai bertipe apa saja? Jawabannya adalah empty interface, yang kita bahas pada chapter berikutnya.

Referensi #

Berkas Sumber

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

implicit-demo.go

Unduh berkas
go
package main

import "fmt"

// PemberiSalam hanya mendefinisikan kontrak, bukan implementasi.
type PemberiSalam interface {
	Salam() string
}

// Pelanggan dan Kurir tidak menyebut PemberiSalam pada deklarasinya.
type Pelanggan struct {
	Nama string
}

func (p Pelanggan) Salam() string {
	return "Halo, " + p.Nama + "!"
}

type Kurir struct {
	Kode string
}

func (k Kurir) Salam() string {
	return "Paket milik kurir " + k.Kode + " siap diambil."
}

// Cek kompilasi: kedua tipe wajib memenuhi PemberiSalam, jika tidak build gagal.
var _ PemberiSalam = Pelanggan{}
var _ PemberiSalam = Kurir{}

// sapaSemua bekerja untuk semua tipe yang punya method Salam.
func sapaSemua(daftar []PemberiSalam) {
	for _, s := range daftar {
		fmt.Println(s.Salam())
	}
}

func main() {
	daftar := []PemberiSalam{
		Pelanggan{Nama: "Budi"},
		Kurir{Kode: "K-07"},
	}
	sapaSemua(daftar)
}