পাঠ ৪.৩

Slice Type

The Slice Type

Slice হলো একটা collection-এর কিছু পরপর element-এর reference। Slice-ও এক ধরনের reference, তাই ownership নেয় না — কিন্তু কোন range-এ point করছে সেই information রাখে।

সমস্যা — first word খোঁজা

এই function লিখব — একটা string-এ space-separated word আছে, প্রথম word-এর শেষ index return করো। Space না থাকলে পুরো string-ই এক word, string-এর length return করো।

Slice ছাড়া প্রথম try:

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

কী হচ্ছে — .as_bytes() String-কে byte array-এ convert করে। .iter() iterator, .enumerate() প্রতিটার সাথে index pair করে tuple-এ wrap করে — যা আমরা (i, &item) দিয়ে destructure করছি। b' ' হলো space-এর byte value। প্রথম space পেলে index return; না পেলে length।

এই approach-এ ছোট কিন্তু বড় সমস্যা

Function একটা usize return করছে — string-এর সাথে এর কোনো সম্পর্ক নেই। String পরিবর্তন হলে সেই index meaningless হয়ে যায়:

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s); // word will get the value 5

    s.clear(); // this empties the String, making it equal to ""

    // word still has the value 5 here, but s no longer has any content that we
    // could meaningfully use with the value 5, so word is now totally invalid!
}

word-এর value 5, কিন্তু s এখন empty। 5 এখন কোনো অর্থ বহন করছে না। Compiler এই sync error ধরবে না।

Function-টা যদি second_word-ও বানাই — তখন start ও end দুটো index track করতে হবে: (usize, usize)। এই ধরনের brittle code-এর সমাধান — slice

String slice — সমাধান

String slice একটা String-এর কিছু পরপর element-এর reference:

fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];
}

Syntax — &s[start..end]। start-থেকে শুরু, end-এর আগে শেষ। Slice-এর ভিতরে — pointer (start position-এ) এবং length (end − start)। যেমন world — pointer byte 6-তে, length 5।

Range syntax-এর shortcut

0 থেকে শুরু হলে start বাদ:

let s = String::from("hello");

let slice = &s[0..2];
let slice = &s[..2];

শেষ পর্যন্ত হলে end বাদ:

let s = String::from("hello");

let len = s.len();

let slice = &s[3..len];
let slice = &s[3..];

পুরো string slice — দুটোই বাদ:

let s = String::from("hello");

let len = s.len();

let slice = &s[0..len];
let slice = &s[..];

(Note: range index অবশ্যই UTF-8 character boundary-তে হতে হবে — মাঝে ভাঙলে runtime panic।)

first_word slice দিয়ে

Slice-এর type হলো &str ("string slice" পড়ো):

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

এখন return value data-র সাথে যুক্ত — শুধু একটা lone index না। Reference + length — দুটোই আছে।

Compiler আগের bug ধরে দিচ্ছে

Slice-এর সবচেয়ে সুন্দর দিক — আগে যে bug সম্ভব ছিল, এখন compile error:

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // error!

    println!("the first word is: {word}");
}
compile errortext
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
  --> src/main.rs:18:5
   |
16 |     let word = first_word(&s);
   |                           -- immutable borrow occurs here
17 |
18 |     s.clear(); // error!
   |     ^^^^^^^^^ mutable borrow occurs here
19 |
20 |     println!("the first word is: {word}");
   |                                   ---- immutable borrow later used here

word একটা immutable borrow ধরে রেখেছে। .clear()-কে mutable borrow লাগে। Borrowing rule violate — তাই compile error। Rust এই entire class-এর bug eliminate করেছে।

String literal-ও আসলে slice

let s = "Hello, world!";

এই s-এর type &str — binary-র মধ্যে hardcoded যে memory আছে, তার একটা slice। এই কারণে literal immutable — &str immutable reference।

&str parameter — flexible API

&String-এর জায়গায় &str নিলে function অনেক বেশি usable:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let my_string = String::from("hello world");

    // `first_word` works on slices of `String`s, whether partial or whole.
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` also works on references to `String`s, which are equivalent
    // to whole slices of `String`s.
    let word = first_word(&my_string);

    let my_string_literal = "hello world";

    // `first_word` works on slices of string literals, whether partial or
    // whole.
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);

    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

&String থেকে &str-এ conversion automatic হয় — deref coercion বলে এটাকে (Chapter 15-এ বিস্তারিত)। তাই &str নেওয়া best practice।

Array slice

Slice শুধু string-এ না — যেকোনো collection-এ কাজ করে:

let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];

assert_eq!(slice, &[2, 3]);

এই slice-এর type &[i32]। String slice-এর মতোই — first element-এর reference + length। Vector (Chapter 8) এবং অন্যান্য collection-এর জন্যও same pattern।

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

  • Slice — collection-এর consecutive range-এর reference; ownership নেয় না।
  • String slice type &str; syntax &s[start..end]; shortcut &s[..end], &s[start..], &s[..]
  • String literal-এর type-ই &str — তাই literal immutable।
  • &str parameter &String-এর চেয়ে flexible — দুটোই accept করে।
  • Array slice &[T] — same pattern, যেকোনো collection-এ কাজ করে।
  • Slice + borrow-rule-এর combination compile-time-এ সমস্ত index/string sync bug ধরে দেয়।