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.

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:

rust
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:

text
Halo, dunia!
panjang: 12

Mari kita bedah kode di atas baris per baris:

  1. Baris 2 (let mut sentence = String::new();): sentence lahir sebagai String kosong dengan panjang 0. Kata kunci mut wajib karena isinya akan diubah.
  2. Baris 3 (sentence.push_str("Halo");): empat byte "Halo" ditempel di ujung buffer. Buffer kosong tadi kini berisi Halo.
  3. Baris 4 (sentence.push_str(", dunia");): delapan byte lagi menyusul sehingga teks menjadi Halo, dunia. Jika kapasitas tidak mencukupi, realokasi terjadi di baris ini.
  4. Baris 5 (sentence.push('!');): push menerima satu char — perhatikan kutip tunggal — dan menempelkannya sebagai byte terakhir. Teks kini Halo, dunia!.
  5. 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:

rust
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:

text
hasil: Belajar Rust
second masih bisa dipakai: Rust

Mari kita telaah baris-barisnya:

  1. Baris 2-3 (let first = ...; let second = ...;): dua String independen dibuat, masing-masing memiliki buffer heap sendiri.
  2. Baris 4 (let combined = first + &second;): first dipindahkan ke dalam hasil — kepemilikan buffernya berpindah ke combined. &second hanya dipinjam; setelah penjumlahan selesai, pinjaman ditutup dan second tidak kehilangan apa-apa.
  3. Baris 5 (println!("hasil: {}", combined);): hasil penjumlahan berisi teks gabungan Belajar Rust.
  4. Baris 6 (println!("second masih bisa dipakai: {}", second);): second masih hidup karena hanya dipinjam. Baris ini tidak akan bisa dilakukan pada first — alasannya kita lihat sebentar lagi.

Konsekuensi kepemilikan operan kiri langsung terasa ketika kita mencoba memakai first setelah dipindahkan. Kode berikut gagal dikompilasi:

rust
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:

text
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:

rust
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:

text
Belajar Rust itu menyenangkan
Panduan Rust

Bedah ringkasnya:

  1. Baris 6 (let combined = first + &second + &third + &fourth;): penjumlahan dievaluasi dari kiri — first dipindahkan sekali, lalu hasilnya (sudah berupa String) menampung pinjaman &third dan &fourth bergantian. Hanya satu kepemilikan yang beredar dari awal sampai akhir.
  2. Baris 10 (title += "Rust";): += menempelkan "Rust" di ujung title — perilaku sama dengan push_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:

rust
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:

text
Budi Santoso belajar Rust
Budi Santoso

Mari kita bedah baris-barisnya:

  1. Baris 2 (let mut sentence = String::from("Budi belajar Rust");): teks awal berisi Budi belajar Rust; huruf B u d i menempati byte 0 sampai 3 dan byte 4 adalah spasi.
  2. Baris 3 (sentence.insert_str(5, "Santoso ");): teks "Santoso " disisipkan mulai byte 5, tepat sebelum huruf b dari belajar. Seluruh teks setelah posisi itu bergeser ke kanan sehingga kalimat menjadi Budi Santoso belajar Rust.
  3. Baris 5 (full_name.insert(4, ' ');): satu spasi disisipkan pada byte 4, memisahkan Budi dari Santoso — 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!.