Kepemilikan buffer di heap hanya berguna jika isinya bisa dibentuk saat program berjalan. Program nyata merangkai teks sedikit demi sedikit: menyambung salam dengan nama, menempelkan satuan di belakang angka, atau menyisipkan nama tengah yang baru diketahui. Chapter ini mengumpulkan perkakas utama pembentukan String — push_str dan push untuk menumbuhkan teks di ujung, insert dan insert_str untuk menyelipkan teks di tengah, serta operator plus untuk menggabungkan potongan-potongan menjadi satu. Masing-masing perkakas membawa konsekuensi kepemilikan yang berbeda, dan perbedaan itulah yang akan kita bedah tuntas.
Menumbuhkan Teks dengan push_str dan push #
Aturan dasarnya: push_str(&str) menempelkan potongan teks di ujung String, sedangkan push(char) menempelkan satu character. Keduanya hanya tersedia pada variabel String yang dideklarasikan dengan mut, karena keduanya mengubah isi buffer. Ketika ruang di buffer habis, mekanisme realokasi dari Chapter Tipe String bekerja otomatis — kita tidak perlu mengelola kapasitas secara manual.
Mari kita bangun sebuah kalimat dari nol:
fn main() {
let mut sentence = String::new();
sentence.push_str("Halo");
sentence.push_str(", dunia");
sentence.push('!');
println!("{}", sentence);
println!("panjang: {}", sentence.len());
}Output yang Dihasilkan:
Halo, dunia!
panjang: 12Mari kita bedah kode di atas baris per baris:
- Baris 2 (
let mut sentence = String::new();):sentencelahir sebagaiStringkosong dengan panjang 0. Kata kuncimutwajib karena isinya akan diubah. - Baris 3 (
sentence.push_str("Halo");): empat byte"Halo"ditempel di ujung buffer. Buffer kosong tadi kini berisiHalo. - Baris 4 (
sentence.push_str(", dunia");): delapan byte lagi menyusul sehingga teks menjadiHalo, dunia. Jika kapasitas tidak mencukupi, realokasi terjadi di baris ini. - Baris 5 (
sentence.push('!');):pushmenerima satuchar— perhatikan kutip tunggal — dan menempelkannya sebagai byte terakhir. Teks kiniHalo, dunia!. - Baris 6-7 (
println!…): hasil akhirnya 12 byte: 11 huruf dan tanda seru.
Pola String::new() lalu push_str berulang adalah cara paling lazim mengisi teks dari potongan yang datang bertahap, misalnya baris-baris berkas yang dibaca satu per satu.
Operator Plus dan Kepemilikan #
Operator + pada teks punya aturan kepemilikan yang khas: operan kiri dipindahkan, operan kanan hanya dipinjam. Hasil penjumlahan adalah String baru yang meneruskan buffer operan kiri (dengan realokasi bila perlu), sementara operan kanan tetap milik pemiliknya. Karena itu bentuk penjumlahan yang sehat adalah first + &second — kiri String, kanan pinjaman &String yang otomatis diperlakukan sebagai &str:
fn main() {
let first = String::from("Belajar ");
let second = String::from("Rust");
let combined = first + &second;
println!("hasil: {}", combined);
println!("second masih bisa dipakai: {}", second);
}Output yang Dihasilkan:
hasil: Belajar Rust
second masih bisa dipakai: RustMari kita telaah baris-barisnya:
- Baris 2-3 (
let first = ...; let second = ...;): duaStringindependen dibuat, masing-masing memiliki buffer heap sendiri. - Baris 4 (
let combined = first + &second;):firstdipindahkan ke dalam hasil — kepemilikan buffernya berpindah kecombined.&secondhanya dipinjam; setelah penjumlahan selesai, pinjaman ditutup dansecondtidak kehilangan apa-apa. - Baris 5 (
println!("hasil: {}", combined);): hasil penjumlahan berisi teks gabunganBelajar Rust. - Baris 6 (
println!("second masih bisa dipakai: {}", second);):secondmasih hidup karena hanya dipinjam. Baris ini tidak akan bisa dilakukan padafirst— alasannya kita lihat sebentar lagi.
Konsekuensi kepemilikan operan kiri langsung terasa ketika kita mencoba memakai first setelah dipindahkan. Kode berikut gagal dikompilasi:
fn main() {
let first = String::from("Belajar ");
let second = String::from("Rust");
let combined = first + &second;
println!("first masih: {}", first);
println!("hasil: {}", combined);
}Pesan error kompilator:
error[E0382]: borrow of moved value: `first`
--> main.rs:5:33
|
2 | let first = String::from("Belajar ");
| ----- move occurs because `first` has type `String`, which does not implement the `Copy` trait
3 | let second = String::from("Rust");
4 | let combined = first + &second;
| ----- value moved here
5 | println!("first masih: {}", first);
| ^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
4 | let combined = first.clone() + &second;
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.Pesan E0382 menegaskan arah kepemilikan: nilai berpindah pada baris ke-4 karena + memakan operan kirinya, sehingga first sudah mati ketika baris ke-5 mencoba meminjamnya. Kompilator menawarkan dua jalan keluar: clone() bila first memang masih dibutuhkan, atau cukup hapus pemakaian first bila tidak.
Penjumlahan bisa dirantai karena hasil tiap tahap adalah String, dan operator += juga tersedia untuk menempel pinjaman di ujung variabel yang sama:
fn main() {
let first = String::from("Belajar");
let second = String::from(" Rust");
let third = String::from(" itu");
let fourth = String::from(" menyenangkan");
let combined = first + &second + &third + &fourth;
println!("{}", combined);
let mut title = String::from("Panduan");
title.push(' ');
title += "Rust";
println!("{}", title);
}Output yang Dihasilkan:
Belajar Rust itu menyenangkan
Panduan RustBedah ringkasnya:
- Baris 6 (
let combined = first + &second + &third + &fourth;): penjumlahan dievaluasi dari kiri —firstdipindahkan sekali, lalu hasilnya (sudah berupaString) menampung pinjaman&thirddan&fourthbergantian. Hanya satu kepemilikan yang beredar dari awal sampai akhir. - Baris 10 (
title += "Rust";):+=menempelkan"Rust"di ujungtitle— perilaku sama denganpush_str, hanya bahasa operator.
Menyisipkan Teks dengan insert dan insert_str #
Ketika posisi penyisipan bukan di ujung, insert_str(byte_index, &str) menyelipkan teks pada posisi byte tertentu, dan insert(byte_index, char) menyisipkan satu character. Semua byte setelah posisi tersebut digeser ke kanan — biayanya linear, jadi untuk penambahan massal pola push_str tetap yang termurah:
fn main() {
let mut sentence = String::from("Budi belajar Rust");
sentence.insert_str(5, "Santoso ");
println!("{}", sentence);
let mut full_name = String::from("BudiSantoso");
full_name.insert(4, ' ');
println!("{}", full_name);
}Output yang Dihasilkan:
Budi Santoso belajar Rust
Budi SantosoMari kita bedah baris-barisnya:
- Baris 2 (
let mut sentence = String::from("Budi belajar Rust");): teks awal berisiBudi belajar Rust; hurufBudimenempati byte 0 sampai 3 dan byte 4 adalah spasi. - Baris 3 (
sentence.insert_str(5, "Santoso ");): teks"Santoso "disisipkan mulai byte 5, tepat sebelum hurufbdaribelajar. Seluruh teks setelah posisi itu bergeser ke kanan sehingga kalimat menjadiBudi Santoso belajar Rust. - Baris 5 (
full_name.insert(4, ' ');): satu spasi disisipkan pada byte 4, memisahkanBudidariSantoso— cara cepat memperbaiki nama yang tergabung.
Ringkasan #
push_str dan push menumbuhkan teks di ujung dengan realokasi otomatis; insert dan insert_str menyisipkan pada posisi byte mana pun asalkan di batas character; operator + menggabungkan dengan memindahkan operan kiri dan meminjam operan kanan, sementara += menempel pinjaman di ujung. Kesalahan memakai operan kiri setelah dipindahkan ditangkap E0382 lengkap dengan saran perbaikan. Ketika potongan yang digabungkan banyak, rantai + mulai berisik — dan di situlah format! mengambil alih, bintang Chapter Formatting dengan format!.