Interface menyembunyikan tipe konkretnya, tetapi ada saat ketika tipe aslinya perlu ditanyakan kembali. Topik ini membahas type assertion untuk mengambil nilai konkret dari variabel interface, bentuk comma-ok yang aman, type switch yang menangani banyak tipe sekaligus, serta asersi ke interface lain untuk memeriksa kemampuan. Setelah membacanya, data yang masuk dengan tipe yang belum diketahui dapat ditangani tanpa mengorbankan pemeriksaan tipe.
Ekspresi type assertion#
Type assertion (type assertion) adalah ekspresi x.(T) yang menanyakan apakah tipe dinamis dari x adalah T, lalu mengambil nilai itu dalam bentuk T. Variabel x wajib bertipe interface, sedangkan T boleh berupa tipe konkret maupun interface lain. Ekspresi ini memiliki dua bentuk, yaitu bentuk tunggal yang memicu panic saat tipe tidak cocok dan bentuk comma-ok yang melaporkan kecocokan lewat nilai bool.
// tidak-dijalankan
func baca(x any) (string, int, bool) {
teks := x.(string) // bentuk tunggal, panic bila tipe tidak cocok
angka, ok := x.(int) // bentuk comma-ok, tidak pernah panic
return teks, angka, ok
}Program berikut memakai keduanya pada variabel interface yang sama.
package main
import "fmt"
func main() {
var data any = "log sensor suhu"
// bentuk tunggal: hasilnya bertipe string, panic bila tipe tidak cocok
teks := data.(string)
fmt.Println("teks:", teks)
// bentuk comma-ok: tidak pernah panic, kecocokannya diperiksa lewat bool
angka, cocok := data.(int)
fmt.Printf("angka: %d, cocok: %v\n", angka, cocok)
}teks: log sensor suhu
angka: 0, cocok: falseBentuk tunggal data.(string) berhasil karena tipe dinamisnya memang string. Pada bentuk comma-ok, kegagalan tidak memicu panic, melainkan ok bernilai false dan variabel tujuannya diisi nilai nol tipe yang diminta, yaitu 0 untuk int. Bentuk comma-ok menjadi pilihan bawaan setiap kali kecocokan tipe tidak dijamin oleh kode di sekitarnya. Program tersebut dapat dijalankan lewat terminal sebagaimana berikut.
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gointerface/type-assertion
cp asersi-tipe.go /tmp/gointerface/type-assertion/
cd /tmp/gointerface/type-assertion
# periksa format sebelum menjalankan programnya
gofmt -l .
# jalankan programnya
go run asersi-tipe.goteks: log sensor suhu
angka: 0, cocok: falseKeluarannya sama dengan blok sebelumnya karena keduanya menjalankan program yang sama, sedangkan gofmt -l tidak mencetak apa pun ketika format kode sudah sesuai.
Pemeriksaan asersi tidak mengubah isi variabel interface. Apabila asersinya berhasil, nilai dinamis yang sama disajikan dalam tipe yang diminta, sedangkan apabila gagal, nilai tersebut tetap utuh di dalam interface dan pemanggil yang memutuskan langkah berikutnya. Sifat ini membuat asersi dapat dipakai berulang pada satu variabel yang sama dengan tipe tujuan yang berbeda-beda.
Type switch#
Type switch (type switch) memeriksa tipe dinamis terhadap sejumlah kemungkinan sekaligus. Bentuknya adalah pernyataan switch dengan ekspresi khusus nilai.(type), dan setiap case menyebut satu tipe. Variabel yang dideklarasikan pada ekspresi itu mengadopsi tipe dari case yang terpilih, sehingga isinya langsung dapat dipakai tanpa asersi tambahan. Program berikut menangani data sensor yang tipe elemennya bercampur.
package main
import "fmt"
// proses menangani satu nilai dinamis sesuai tipe dinamisnya.
func proses(nilai any) string {
switch v := nilai.(type) {
case string:
return fmt.Sprintf("teks %q", v)
case int:
return fmt.Sprintf("bilangan bulat %d", v)
case float64:
return fmt.Sprintf("bilangan desimal %.1f", v)
case bool:
return fmt.Sprintf("logika %v", v)
default:
return "tipe belum ditangani"
}
}
func main() {
data := []any{"sensor-suhu", 24, 19.5, true}
for _, item := range data {
fmt.Println(proses(item))
}
}teks "sensor-suhu"
bilangan bulat 24
bilangan desimal 19.5
logika trueSetiap baris keluaran dihasilkan oleh case yang berbeda, dan variabel v bertipe string, int, float64, atau bool sesuai cabang yang terpilih. Cabang default menampung tipe yang tidak tercantum, sedangkan case nil khusus menampung variabel interface yang masih nil. Type switch menggantikan rangkaian pernyataan if dengan comma-ok yang berulang, sehingga keputusannya terbaca sebagai satu tabel tipe.
Satu case juga boleh menyebut lebih dari satu tipe, misalnya case int, float64:. Pada cabang semacam itu, variabel v mempertahankan tipe statisnya, yaitu tipe yang ditetapkan pada deklarasi dan berupa any, sehingga isinya baru dapat dipakai setelah pemeriksaan tipe dilakukan. Daftar tipe pada type switch diperiksa berurutan dari atas ke bawah, dan pemilihan berhenti pada cabang yang cocok lebih dahulu.
Asersi ke interface lain#
Target asersi tidak harus tipe konkret. Ekspresi x.(T) dengan T berupa interface memeriksa apakah tipe dinamisnya memenuhi interface tersebut, dan hasilnya tetap bertipe interface. Pemeriksaan semacam ini menanyakan kemampuan dan bukan identitas tipe. Program berikut menguji apakah nilai dinamis dapat mencetak dirinya sendiri.
package main
import "fmt"
// Suhu adalah pengukuran suhu dalam derajat Celsius.
type Suhu float64
// String memenuhi fmt.Stringer.
func (s Suhu) String() string {
return fmt.Sprintf("%.1f °C", float64(s))
}
func main() {
var data any = Suhu(24.5)
// asersi ke interface: yang ditanyakan adalah metode, bukan tipe konkret
s, cocok := data.(fmt.Stringer)
fmt.Println("memenuhi fmt.Stringer:", cocok)
fmt.Println("cetakan Stringer:", s.String())
}memenuhi fmt.Stringer: true
cetakan Stringer: 24.5 °CAsersi berhasil karena Suhu memiliki metode String, sehingga variabel s bertipe fmt.Stringer dan metodenya dapat dipanggil langsung. Pola ini digunakan pustaka standar untuk memeriksa kemampuan tambahan secara opsional, misalnya io.WriterTo dan io.ReaderFrom yang dimanfaatkan io.Copy untuk mempercepat penyalinan bila tersedia. Kegagalan asersi bukanlah error program, melainkan tanda kemampuan itu tidak ada, sehingga jalur cadangan tetap dapat dijalankan.
Kesalahan umum#
Kesalahan pada asersi baru muncul saat program berjalan, sehingga tiga pola berikut perlu dijaga sejak awal.
- Memakai bentuk tunggal pada data yang tipenya tidak terkendali. Panic
interface conversion: interface {} is string, not intmuncul saat program berjalan, jauh dari tempat data itu masuk. Perbaikannya, gunakan bentuk comma-ok atau type switch, dan laporkan kegagalannya sebagai error. - Melakukan asersi pada variabel interface yang masih
nil. Panic yang muncul berbunyiinterface conversion: interface {} is nil, not int, dan bentuk comma-ok pun mengembalikanokbernilaifalse. Perbaikannya, periksanilterlebih dahulu bila variabel belum tentu terisi. - Menyusun type switch tanpa cabang
default. Tipe yang tidak terduga lolos tanpa jejak, sehingga kesalahannya baru terlihat setelah keluaran salah. Perbaikannya, tanganidefaultsecara eksplisit, misalnya dengan melaporkan tipe yang belum ditangani melalui verb%T.
Ringkasan#
x.(T)menanyakan tipe dinamis dari variabel interface, dengan bentuk tunggal yang memicu panic dan bentuk comma-ok yang aman.- Bentuk comma-ok mengisi nilai nol tipe tujuan beserta
falseketika tipe tidak cocok. - Type switch memeriksa banyak tipe sekaligus, dan variabel ekspresinya mengadopsi tipe dari cabang yang terpilih.
- Target asersi boleh berupa interface, sehingga yang diperiksa adalah kemampuan dan bukan tipe konkret.
Referensi#
- A Tour of Go: Type assertions — Go Documentation — Latihan resmi bentuk type assertion.
- The Go Programming Language Specification — The Go Project — Aturan resmi type assertion, termasuk target berupa interface.
- The Laws of Reflection — The Go Blog — Penjelasan struktur tipe dan nilai di balik variabel interface.