Teks terlihat sederhana di layar, tetapi di balik layar setiap karakter menyimpan cerita panjang tentang encoding. Go memilih UTF-8 sebagai encoding string bawaan — keputusan yang membuat string Go bisa memuat huruf Latin, aksen Eropa, kanji Jepang, sampai emoji dalam satu bahasa pemrograman tanpa konfigurasi tambahan. Pada chapter ini kita akan membedah string sebagai deret byte UTF-8, mengenal tipe rune sebagai unit karakter Unicode, lalu membuktikan lewat program nyata bahwa panjang string dan hasil iterasinya menyimpan kejutan untuk siapa pun yang belum paham keduanya.
String adalah Deret Byte UTF-8 #
Tipe string menyimpan teks sebagai deret byte (byte sequence) yang di-encode dengan UTF-8. Setiap karakter Latin biasa memakan satu byte, sedangkan karakter lain memakan lebih:
| Karakter | Jenis | Ukuran UTF-8 |
|---|---|---|
S | huruf Latin | 1 byte |
é | e beraksen | 2 byte |
日 | kanji | 3 byte |
🌏 | emoji bumi | 4 byte |
Konsekuensi pentingnya: len(s) menghitung jumlah byte, bukan jumlah karakter yang terlihat mata. String "Siti" berisi 4 byte sekaligus 4 karakter, tetapi "Halo 🌏" berisi 9 byte dengan 6 karakter yang terlihat — lima byte untuk H, a, l, o, spasi, ditambah empat byte untuk satu emoji.
Rune: Satu Unit Karakter Unicode #
Tipe rune adalah nama lain untuk int32 yang dipakai khusus menyimpan satu code point Unicode — satu unit karakter, apa pun bentuk visualnya. Literal rune ditulis di antara petik tunggal: 'S', 'é', '日', '🌏'. Package unicode/utf8 menyediakan fungsi utf8.RuneCountInString untuk menghitung jumlah karakter (rune) di dalam string, yang hasilnya bisa berbeda dari len:
kalimat := "Halo 🌏"
fmt.Println(len(kalimat)) // 9 (byte)
fmt.Println(utf8.RuneCountInString(kalimat)) // 6 (rune)Mengakses satu elemen dengan kalimat[0] juga tidak menghasilkan karakter, melainkan byte pada posisi tersebut bertipe byte (alias uint8). Ekspresi nama[0] pada string "Siti" menghasilkan angka 83 — kode byte huruf S.
Iterasi range: Indeks Byte, Nilai Rune #
Perulangan for ... range pada string memecah teks menjadi rune satu per satu. Perhatikan baik-baik pasangan nilai yang dihasilkannya: variabel pertama berisi indeks byte posisi awal karakter di dalam string, variabel kedua berisi rune hasil dekoding. Struktur perulangan ini akan kita bahas tuntas pada Section Kendali Alur; untuk sekarang, fokuskan perhatian pada pasangan indeks dan karakter yang tercetak.
Pada string "Halo 🌏", rune keenam adalah emoji yang menempati byte nomor 5 sampai 8, sehingga iterasi berhenti melaporkan indeks setelah emoji — bukti bahwa indeks yang dilaporkan adalah posisi byte, bukan nomor karakter.
Program Utuh #
Berkas string-rune-demo.go menggabungkan penghitungan byte, penghitungan rune, iterasi string, dan pengambilan byte tunggal. Jalankan berkas tersebut dari direktori chapter:
go run string-rune-demo.gopackage main
import (
"fmt"
"unicode/utf8"
)
func main() {
nama := "Siti"
fmt.Printf("%q: %d byte, %d rune\n", nama, len(nama), utf8.RuneCountInString(nama))
kalimat := "Halo 🌏"
fmt.Printf("%q: %d byte, %d rune\n", kalimat, len(kalimat), utf8.RuneCountInString(kalimat))
for indeks, karakter := range kalimat {
fmt.Printf("indeks=%d karakter=%q\n", indeks, karakter)
}
fmt.Println("Byte pertama nama:", nama[0])
fmt.Printf("Rune dari byte 83: %c\n", rune(nama[0]))
}Output yang Dihasilkan:
"Siti": 4 byte, 4 rune
"Halo 🌏": 9 byte, 6 rune
indeks=0 karakter='H'
indeks=1 karakter='a'
indeks=2 karakter='l'
indeks=3 karakter='o'
indeks=4 karakter=' '
indeks=5 karakter='🌏'
Byte pertama nama: 83
Rune dari byte 83: SMari kita telaah baris demi baris program di atas:
- Baris 10 (
nama := "Siti"): mendeklarasikan string murni ASCII — jumlah byte dan jumlah runenya sama-sama 4. - Baris 14 (
kalimat := "Halo 🌏"): mendeklarasikan string multibyte;lenmelaporkan 9 byte,utf8.RuneCountInStringmelaporkan 6 rune. - Baris 17 (
for indeks, karakter := range kalimat): mengiterasi string per rune; verb%qdipakai agar spasi terlihat sebagai' 'dan tidak terlewat oleh mata. - Baris 18 (
fmt.Printf("indeks=%d karakter=%q\n", ...)): mencetak pasangan indeks-karakter; indeks terakhir yang muncul adalah 5, padahal string panjangnya 9 byte — byte 6, 7, dan 8 adalah kelanjutan encoding emoji yang sudah terwakili rune di indeks 5. - Baris 21 (
nama[0]): mengambil byte pertama"Siti"berupa angka83, bukan hurufS. - Baris 22 (
rune(nama[0])): mengonversi byte 83 menjadi rune lalu mencetaknya dengan verb%csehingga tampil sebagai hurufS.
String Tidak Bisa Diubah Isinya #
String bersifat immutable: sekali dibuat, isi bytenya tidak bisa dimodifikasi satu per satu. Penugasan nama[0] = 'M' ditolak compiler dengan pesan cannot assign to nama[0]. Mengubah teks berarti membuat string baru, misalnya lewat penggabungan "M" + nama[1:], dan hasil penggabungan inilah yang disimpan pada variabel.
Dengan pemahaman byte dan rune, kita siap masuk lebih dalam ke representasi teks di memori: bagaimana byte dan string saling dikonversi, dan bagaimana memastikan data byte yang kita terima adalah UTF-8 yang sah.
Referensi #
- Blog Go: Strings, bytes, runes and characters — artikel klasik yang membedah topik chapter ini secara mendalam.
- Spesifikasi Go: String types — definisi resmi tipe string.
- Dokumentasi package unicode/utf8 — fungsi penghitung dan validator rune.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
string-rune-demo.go
Unduh berkaspackage main
import (
"fmt"
"unicode/utf8"
)
func main() {
nama := "Siti"
fmt.Printf("%q: %d byte, %d rune\n", nama, len(nama), utf8.RuneCountInString(nama))
kalimat := "Halo 🌏"
fmt.Printf("%q: %d byte, %d rune\n", kalimat, len(kalimat), utf8.RuneCountInString(kalimat))
for indeks, karakter := range kalimat {
fmt.Printf("indeks=%d karakter=%q\n", indeks, karakter)
}
fmt.Println("Byte pertama nama:", nama[0])
fmt.Printf("Rune dari byte 83: %c\n", rune(nama[0]))
}