Sepanjang Course ini kita terus-menerus menulis teks di dalam tanda kutip ganda — mulai dari "Halo, dunia!" hingga "Belajar Rust" — tanpa pernah bertanya di mana teks tersebut sebenarnya disimpan. Kita juga sudah mengenal dua wujud teks di Section Ownership: String yang dimiliki penuh dan &str yang hanya dipinjam. Section ini mulai membedah kedua wujud tersebut sampai ke akarnya, dan titik awalnya adalah string literal: potongan teks yang ditulis langsung dalam kode sumber. Setelah Chapter ini, kita akan memahami bahwa setiap literal "..." yang kita tulis sebenarnya sudah tertanam di dalam program jadi sejak kompilasi, dan tipe &str adalah jendela pinjaman yang menatap ke sana.
String Literal yang Tersimpan di Binary #
Aturannya sederhana: setiap teks yang ditulis langsung di dalam tanda kutip ganda pada kode sumber disebut string literal, dan kompilator menyalin byte-byte teks tersebut ke dalam binary — file program jadi hasil kompilasi — pada area memori yang bersifat hanya-baca. Saat program dijalankan, sistem operasi memuat binary ke memori, dan byte literal tersebut sudah tersedia lengkap tanpa perlu dialokasikan ulang di heap. Tipe literal bukan String, melainkan &str: sebuah reference yang menunjuk ke byte-byte tersebut lengkap dengan panjangnya.
Mekanisme penyimpanannya dapat digambarkan sebagai berikut:
Karena literal tertanam di binary sejak awal, umur literal sama panjang dengan umur program — byte-nya tidak pernah hilang selama program masih berjalan, dan tidak ada siapa pun yang perlu membebaskannya. Mari kita buktikan bahwa literal memang bekerja sebagai nilai &str yang utuh:
fn main() {
let greeting = "Halo, Rust!";
println!("{}", greeting);
println!("panjang dalam byte: {}", greeting.len());
}Output yang Dihasilkan:
Halo, Rust!
panjang dalam byte: 11Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let greeting = "Halo, Rust!";): variabelgreetingmengikat sebuah string literal. Tipe yang diinferensikan Rust adalah&str— reference menuju teks di binary — bukanStringyang dimiliki. - Baris 3 (
println!("{}", greeting);): teks dicetak dengan placeholder{}sebagaimana biasa. Tidak ada proses alokasi memori baru di sini;println!hanya membaca byte yang ditunjuk. - Baris 4 (
println!("panjang dalam byte: {}", greeting.len());): metodelen()melaporkan panjang teks dalam satuan byte."Halo, Rust!"terdiri atas 11 byte karena seluruh hurufnya adalah karakter ASCII yang masing-masing menempati satu byte.
&str Sebagai Tipe Pinjaman #
Tipe &str dibaca “reference to str”. str adalah tipe “teks mentah berformat UTF-8”, dan &str adalah pinjaman berupa penunjuk posisi awal plus panjang byte — persis pola slice yang sudah kita kenal di Section Slice dan Referensi. Sebuah &str tidak memiliki memori apa pun; &str hanya menatap teks yang dimiliki pihak lain, entah teks literal di binary ataupun isi String di heap.
Konsekuensi praktisnya luar biasa nyaman untuk fungsi: parameter bertipe &str dapat menerima string literal maupun pinjaman dari String. Kompiler Rust secara otomatis mengubah pinjaman &String menjadi &str saat nilai dikirim ke fungsi, karena isi keduanya sama-sama byte UTF-8 yang berjejer. Perhatikan fungsi berikut:
fn count_bytes(text: &str) -> usize {
text.len()
}
fn main() {
let literal = "Belajar Rust";
let owned = String::from("Pemrograman Rust");
println!("literal: {} byte", count_bytes(literal));
println!("owned: {} byte", count_bytes(&owned));
}Output yang Dihasilkan:
literal: 12 byte
owned: 16 byteMari kita telaah peran setiap baris:
- Baris 1 (
fn count_bytes(text: &str) -> usize {): fungsi hanya meminta pinjaman teks bertipe&str, sehingga fungsi tidak pernah mengambil kepemilikan apa pun dari pemanggil. - Baris 2 (
text.len()): panjang teks dibaca dalam byte lewat pinjaman, lalu dikembalikan sebagaiusize. - Baris 5 (
let literal = "Belajar Rust";):literalbertipe&strkarena diikat langsung ke string literal. Nilai ini dikirim ke fungsi tanpa tanda&tambahan — nilainya memang sudah berupa reference. - Baris 6 (
let owned = String::from("Pemrograman Rust");):ownedbertipeStringyang memiliki teks di heap, pola yang akan kita dalami di Chapter Tipe String. - Baris 7 (
count_bytes(literal)): literal dikirim apa adanya karena tipenya sudah&str. - Baris 8 (
count_bytes(&owned)):&ownedmembuat pinjaman&String, lalu Rust mengubahnya otomatis menjadi&stragar cocok dengan parameter. Teks di heap tidak tersalin; yang berpindah hanya penunjuk dan panjangnya.
Literal, Byte, dan Karakter Non-ASCII #
Literal dalam Rust di-encode dengan UTF-8, standar encoding yang sama dengan isi String. Artinya, literal yang berisi karakter di luar ASCII tetap sah, tetapi jumlah byte-nya tidak lagi sama dengan jumlah character-nya. Mari kita uji dengan satu huruf beraksen:
fn main() {
let text: &str = "café";
println!("{}", text);
println!("panjang dalam byte: {}", text.len());
}Output yang Dihasilkan:
café
panjang dalam byte: 5Bedah singkatnya:
- Baris 2 (
let text: &str = "café";): anotasi tipe&strditulis eksplisit di sini untuk menegaskan tipe literal. Anotasi ini opsional — tanpa anotasi pun Rust tetap menginferensikan&str. - Baris 4 (
println!("panjang dalam byte: {}", text.len());):len()melaporkan 5, padahal teks terlihat berisi empat huruf. Hurufémenempati dua byte dalam UTF-8, sehingga totalnya menjadi tiga huruf ASCII satu byte plus satu huruf dua byte = 5 byte.
Fenomena “4 character tapi 5 byte” ini adalah benang merah seluruh Section: teks Rust selalu UTF-8, dan banyak operasi teks bekerja di level byte. Kita akan membongkar konsekuensinya di Chapter Mengapa String Tidak Bisa Diindeks.
Kesalahan Klasik: str Tanpa Tanda & #
Pemula yang baru mengenal &str kadang mencoba mengikat literal ke tipe str — tanpa tanda & — karena merasa str adalah “tipe teksnya”. Kode berikut menolak dikompilasi:
fn main() {
let text: str = "Halo";
println!("{}", text);
}Inti pesan error kompilator (sebagian keluaran dipangkas):
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> main.rs:2:9
|
2 | let text: str = "Halo";
| ^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: all local variables must have a statically known size
help: consider borrowing here
|
2 | let text: &str = "Halo";
| +Ringkasan #
String literal tertanam di binary sejak kompilasi, hidup di area read-only selama program berjalan, dan tipenya adalah &str — pinjaman berupa penunjuk plus panjang byte. Fungsi dengan parameter &str adalah titik temu universal: fungsi semacam itu menerima literal secara langsung dan menerima &String melalui konversi otomatis, tanpa menyalin isi teks. Metode len() menghitung byte, bukan character, sehingga teks non-ASCII seperti "café" melaporkan 5. Pertanyaan berikutnya sudah menantang: bagaimana jika kita butuh teks yang tumbuh, berubah, dan dimiliki penuh pada saat program berjalan? Jawabannya adalah String, bintang Chapter selanjutnya.