পাঠ ৮.২

String দিয়ে UTF-8 text রাখা

Storing UTF-8 Encoded Text with Strings

Rust-এর core-এ দু'টা string type:

  • &str — borrowed, immutable string slice।
  • String — standard library থেকে; growable, mutable, owned, UTF-8 encoded।

String আসলে একটা Vec<u8>-এর wrapper — extra guarantee সহ।

String তৈরি

let mut s = String::new();

Literal থেকে — দু'রকমেই কাজ করে:

let s = "initial contents".to_string();
let s = String::from("initial contents");

String::from এবং .to_string() একই কাজ করে — style choice।

UTF-8 — সব valid:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שלום");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
let hello = String::from("নমস্কার");

Update — push_str ও push

fn main() {
    let mut s = String::from("foo");
    s.push_str("bar");
    // s == "foobar"
}

push_str &str নেয় — ownership নেয় না, তাই original valid থাকে:

fn main() {
    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {s2}");
}

push একটা single character যোগ করে:

fn main() {
    let mut s = String::from("lo");
    s.push('l');
    // s == "lol"
}

+ operator দিয়ে concatenate

fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // note s1 has been moved here
}

এর behavior দেখতে অদ্ভুত। ভিতরে কী হচ্ছে দেখি।

+ operator add method call করে:

fn add(self, s: &str) -> String { ... }

দু'টো লক্ষণীয় বিষয়:

  1. s2-এর আগে & দরকার — কারণ add &str চায়। &s2 &String দেয় — Rust deref coercion দিয়ে &str-এ convert করে নেয়।
  2. s1 move হয়add self নেয় (reference না)। তাই +-এর পরে s1 ব্যবহার করা যাবে না।

format! macro — cleaner

অনেকগুলো string concatenate করতে + verbose:

let s = s1 + "-" + &s2 + "-" + &s3;

format! অনেক ভালো:

fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");
}

format! println!-এর মতো — কিন্তু print না করে String return করে। এটা reference নেয় — কোনো argument move হয় না।

String index — allowed না

let s1 = String::from("hi");
let h = s1[0];
compile errortext
error[E0277]: the type `str` cannot be indexed by `{integer}`

কেন? — কারণ String = Vec<u8>। প্রতিটা byte access করতে দিলে UTF-8 character-এর মাঝখানে কেটে যাওয়ার সম্ভাবনা।

উদাহরণ: "Hola" — ৪ byte (প্রতি letter ১ byte)। কিন্তু "Здравствуйте" — ২৪ byte (প্রতি Cyrillic letter ২ byte)। index 0 দিলে byte 208 — কোনো character না, З-এর প্রথম byte।

UTF-8 কে ৩-ভাবে দেখা যায়

নমস্কার-এর hindi version नमस्ते-কে দেখো:

  • Bytes (১৮টা): [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
  • Unicode scalar value (char, ৬টা): ['न', 'म', 'स', '्', 'त', 'े'] — ৪র্থ এবং ৬ষ্ঠ diacritic, একা অর্থহীন।
  • Grapheme cluster (মানুষের দেখা letter, ৪টা): ["न", "म", "स्", "ते"]

Rust তিনটা interpretation-এর জন্য আলাদা method দেয়, যাতে তুমিই ঠিক করতে পার কোন level-এ কাজ করবে।

String slice — সাবধানে

Range দিয়ে slice করা যায়:

let hello = "Здравствуйте";

let s = &hello[0..4]; // s == "Зд" (প্রতি Cyrillic letter ২ byte)

কিন্তু character-এর মাঝখানে slice করলে runtime panic:

runtime panictext
thread 'main' panicked at src/main.rs:4:19:
byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`

Iterate — chars() ও bytes()

for c in "Зд".chars() {
    println!("{c}");
}
// З
// д
for b in "Зд".bytes() {
    println!("{b}");
}
// 208
// 151
// 208
// 180

Grapheme cluster-এর iteration standard library-তে নেই — crates.io-এর third-party crate লাগবে। কারণ — grapheme cluster computation surprisingly complex।

এই পাঠ থেকে যা শিখলে

  • String = Vec<u8> wrapper, UTF-8 encoded। Create: String::new(), String::from(), .to_string()
  • push_str (str), push (char), + (left moves), format! (cleanest, no move)।
  • String index allowed না — UTF-8 byte boundary-এর জন্য।
  • Slice possible (&s[0..4]) কিন্তু character boundary-এ না হলে panic।
  • .chars(), .bytes() — দু'রকম iteration; grapheme cluster তৃতীয়-পক্ষ crate লাগে।