Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

রেফারেন্স এবং ধার করা (References and Borrowing)

তালিকা ৪-৫ এর টাপল (tuple) কোডের সমস্যাটি হলো, calculate_length ফাংশনে String পাস করার পর সেটির মালিকানা চলে যায়, তাই ফাংশন কলের পরেও String ব্যবহার করতে হলে আমাদের আবার সেটি কলিং ফাংশনে ফেরত পাঠাতে হয়। এর পরিবর্তে, আমরা String মানটির একটি রেফারেন্স (reference) পাস করতে পারি। একটি রেফারেন্স অনেকটা পয়েন্টারের মতো, কারণ এটি একটি ঠিকানা যা অনুসরণ করে আমরা সেই ঠিকানায় থাকা ডেটা অ্যাক্সেস করতে পারি; সেই ডেটার মালিক অন্য কোনো ভ্যারিয়েবল। পয়েন্টারের সাথে এর পার্থক্য হলো, একটি রেফারেন্স তার জীবনকাল পর্যন্ত একটি নির্দিষ্ট টাইপের বৈধ মানের দিকে নির্দেশ করার নিশ্চয়তা দেয়।

এখানে দেখানো হলো কীভাবে আপনি একটি calculate_length ফাংশন তৈরি ও ব্যবহার করতে পারেন যা মানের মালিকানা না নিয়ে বরং একটি অবজেক্টের রেফারেন্স প্যারামিটার হিসেবে গ্রহণ করে:

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

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

প্রথমত, লক্ষ্য করুন যে ভ্যারিয়েবল ঘোষণা এবং ফাংশনের রিটার্ন ভ্যালু থেকে সমস্ত টাপল কোড চলে গেছে। দ্বিতীয়ত, লক্ষ্য করুন যে আমরা calculate_length ফাংশনে &s1 পাস করেছি এবং ফাংশনের সংজ্ঞায় String-এর পরিবর্তে &String নিয়েছি। এই অ্যামপারস্যান্ড (&) চিহ্নগুলো রেফারেন্স বোঝায়, এবং এগুলো আপনাকে কোনো মানের মালিকানা না নিয়েই সেটিকে নির্দেশ করতে দেয়। চিত্র ৪-৬ এই ধারণাটি চিত্রিত করে।

তিনটি টেবিল: s-এর টেবিলটিতে শুধুমাত্র s1-এর টেবিলের একটি পয়েন্টার রয়েছে। s1-এর টেবিলটিতে s1-এর জন্য স্ট্যাক ডেটা রয়েছে এবং এটি হীপের স্ট্রিং ডেটার দিকে নির্দেশ করে।

চিত্র ৪-৬: &String s যেভাবে String s1-কে নির্দেশ করে তার একটি চিত্র

দ্রষ্টব্য: & ব্যবহার করে রেফারেন্সিং করার বিপরীতটি হলো ডি-রেফারেন্সিং (dereferencing), যা ডি-রেফারেন্স অপারেটর, * দিয়ে করা হয়। আমরা অধ্যায় ৮-এ ডি-রেফারেন্স অপারেটরের কিছু ব্যবহার দেখব এবং অধ্যায় ১৫-এ ডি-রেফারেন্সিংয়ের বিস্তারিত আলোচনা করব।

আসুন এখানকার ফাংশন কলটি আরও ঘনিষ্ঠভাবে দেখি:

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

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

&s1 সিনট্যাক্সটি আমাদের একটি রেফারেন্স তৈরি করতে দেয় যা s1-এর মানকে নির্দেশ (refers) করে কিন্তু এর মালিকানা নেয় না। যেহেতু রেফারেন্সটির মালিকানা নেই, তাই রেফারেন্সটি ব্যবহার শেষ হয়ে গেলে এটি যে মানটিকে নির্দেশ করে তা ড্রপ (dropped) করা হবে না।

একইভাবে, ফাংশনের সিগনেচার & ব্যবহার করে নির্দেশ করে যে প্যারামিটার s-এর টাইপ একটি রেফারেন্স। আসুন কিছু ব্যাখ্যামূলক টীকা যোগ করি:

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

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because s does not have ownership of what
  // it refers to, the String is not dropped.

যে স্কোপে s ভ্যারিয়েবলটি বৈধ, তা যেকোনো ফাংশন প্যারামিটারের স্কোপের মতোই, কিন্তু s-এর ব্যবহার শেষ হলে রেফারেন্স দ্বারা নির্দেশিত মানটি ড্রপ করা হয় না, কারণ s-এর মালিকানা নেই। যখন ফাংশনগুলো আসল মানের পরিবর্তে রেফারেন্সকে প্যারামিটার হিসাবে ব্যবহার করে, তখন মালিকানা ফিরিয়ে দেওয়ার জন্য আমাদের মানগুলো ফেরত পাঠাতে হবে না, কারণ আমাদের কখনোই মালিকানা ছিল না।

একটি রেফারেন্স তৈরি করার এই প্রক্রিয়াকে আমরা ধার করা (borrowing) বলি। বাস্তব জীবনের মতোই, যদি কোনো ব্যক্তির কোনো কিছুর মালিকানা থাকে, আপনি তাদের কাছ থেকে তা ধার নিতে পারেন। আপনার কাজ শেষ হলে, আপনাকে তা ফিরিয়ে দিতে হবে। আপনি সেটির মালিক নন।

তাহলে, আমরা যদি ধার করা কোনো কিছু পরিবর্তন করার চেষ্টা করি তাহলে কী হবে? তালিকা ৪-৬-এর কোডটি চেষ্টা করুন। স্পয়লার অ্যালার্ট: এটি কাজ করবে না!

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

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

এখানে এররটি হলো:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn change(some_string: &mut String) {
  |                         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

ভ্যারিয়েবলগুলো যেমন ডিফল্টভাবে অপরিবর্তনীয় (immutable), রেফারেন্সও তেমনি। আমাদের কাছে কোনো কিছুর রেফারেন্স থাকলে আমরা তা পরিবর্তন করার অনুমতি পাই না।

পরিবর্তনযোগ্য রেফারেন্স (Mutable References)

আমরা তালিকা ৪-৬ এর কোডটিকে কয়েকটি ছোট পরিবর্তনের মাধ্যমে ঠিক করতে পারি, যাতে আমরা একটি ধার করা মান পরিবর্তন করতে পারি। এর জন্য আমরা একটি পরিবর্তনযোগ্য রেফারেন্স (mutable reference) ব্যবহার করব:

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

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

প্রথমে আমরা s-কে mut করি। তারপর আমরা change ফাংশন কল করার সময় &mut s দিয়ে একটি পরিবর্তনযোগ্য রেফারেন্স তৈরি করি, এবং ফাংশন সিগনেচার আপডেট করে some_string: &mut String দিয়ে একটি পরিবর্তনযোগ্য রেফারেন্স গ্রহণ করি। এটি খুব স্পষ্ট করে দেয় যে change ফাংশনটি তার ধার করা মানটিকে পরিবর্তন করবে।

পরিবর্তনযোগ্য রেফারেন্সের একটি বড় সীমাবদ্ধতা আছে: যদি আপনার কাছে একটি মানের একটি পরিবর্তনযোগ্য রেফারেন্স থাকে, তবে সেই মানের অন্য কোনো রেফারেন্স আপনার কাছে থাকতে পারবে না। s-এর জন্য দুটি পরিবর্তনযোগ্য রেফারেন্স তৈরি করার এই কোডটি ব্যর্থ হবে:

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

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{r1}, {r2}");
}```

</Listing>

এখানে এররটি হলো:

```console
$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{r1}, {r2}");
  |               ---- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

এই এরর বলছে যে এই কোডটি অবৈধ কারণ আমরা একবারে একাধিকবার s-কে পরিবর্তনযোগ্য হিসাবে ধার করতে পারি না। প্রথম পরিবর্তনযোগ্য ধার r1-এ রয়েছে এবং এটি println!-এ ব্যবহৃত না হওয়া পর্যন্ত স্থায়ী হতে হবে, কিন্তু সেই পরিবর্তনযোগ্য রেফারেন্স তৈরির এবং তার ব্যবহারের মধ্যে, আমরা r2-তে আরেকটি পরিবর্তনযোগ্য রেফারেন্স তৈরি করার চেষ্টা করেছি যা r1-এর মতো একই ডেটা ধার করে।

একই ডেটার একাধিক পরিবর্তনযোগ্য রেফারেন্স থাকার সীমাবদ্ধতাটি পরিবর্তন করার সুযোগ দেয় কিন্তু খুব নিয়ন্ত্রিত উপায়ে। নতুন রাস্টেশিয়ানদের (Rustaceans) জন্য এটি একটি সমস্যা কারণ বেশিরভাগ ভাষা আপনাকে যখন ইচ্ছা পরিবর্তন করতে দেয়। এই সীমাবদ্ধতার সুবিধা হলো রাস্ট কম্পাইলের সময় ডেটা রেস (data races) প্রতিরোধ করতে পারে। একটি ডেটা রেস রেস কন্ডিশনের মতোই এবং এটি ঘটে যখন এই তিনটি আচরণ ঘটে:

  • দুই বা ততোধিক পয়েন্টার একই সময়ে একই ডেটা অ্যাক্সেস করে।
  • অন্তত একটি পয়েন্টার ডেটাতে লেখার জন্য ব্যবহৃত হচ্ছে।
  • ডেটা অ্যাক্সেস সিঙ্ক্রোনাইজ করার জন্য কোনো ব্যবস্থা ব্যবহার করা হচ্ছে না।

ডেটা রেসগুলো অনির্ধারিত আচরণের (undefined behavior) কারণ হয় এবং রানটাইমে এগুলো খুঁজে বের করার চেষ্টা করার সময় নির্ণয় এবং ঠিক করা কঠিন হতে পারে; রাস্ট ডেটা রেসসহ কোড কম্পাইল করতে অস্বীকার করে এই সমস্যাটি প্রতিরোধ করে!

বরাবরের মতোই, আমরা কার্লি ব্র্যাকেট ব্যবহার করে একটি নতুন স্কোপ তৈরি করতে পারি, যা একাধিক পরিবর্তনযোগ্য রেফারেন্সের অনুমতি দেয়, শুধু একই সাথে নয়:

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

    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.

    let r2 = &mut s;
}

রাস্ট পরিবর্তনযোগ্য এবং অপরিবর্তনীয় রেফারেন্স একত্রিত করার জন্যও একটি অনুরূপ নিয়ম প্রয়োগ করে। এই কোডটি একটি এরর তৈরি করে:

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{r1}, {r2}, and {r3}");
}

এখানে এররটি হলো:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{r1}, {r2}, and {r3}");
  |               ---- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

আমরা যখন একটি মানের একটি অপরিবর্তনীয় রেফারেন্স রাখি, তখন আমরা একটি পরিবর্তনযোগ্য রেফারেন্সও রাখতে পারি না।

অপরিবর্তনীয় রেফারেন্সের ব্যবহারকারীরা আশা করেন না যে মানটি হঠাৎ করে তাদের অজান্তেই পরিবর্তিত হয়ে যাবে! তবে, একাধিক অপরিবর্তনীয় রেফারেন্স অনুমোদিত কারণ যারা কেবল ডেটা পড়ছে তাদের কেউই অন্য কারো ডেটা পড়াকে প্রভাবিত করতে পারে না।

লক্ষ্য করুন যে একটি রেফারেন্সের স্কোপ যেখানে এটি তৈরি হয় সেখান থেকে শুরু হয় এবং সেই রেফারেন্সটি শেষবার ব্যবহৃত হওয়া পর্যন্ত চলতে থাকে। উদাহরণস্বরূপ, এই কোডটি কম্পাইল হবে কারণ অপরিবর্তনীয় রেফারেন্সগুলোর শেষ ব্যবহার হয় println!-এ, পরিবর্তনযোগ্য রেফারেন্সটি চালু করার আগে:

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{r1} and {r2}");
    // Variables r1 and r2 will not be used after this point.

    let r3 = &mut s; // no problem
    println!("{r3}");
}

অপরিবর্তনীয় রেফারেন্স r1 এবং r2-এর স্কোপ println!-এর পরে শেষ হয়, যেখানে সেগুলি শেষবার ব্যবহৃত হয়, যা পরিবর্তনযোগ্য রেফারেন্স r3 তৈরি হওয়ার আগে। এই স্কোপগুলো ওভারল্যাপ করে না, তাই এই কোডটি অনুমোদিত: কম্পাইলার বলতে পারে যে স্কোপ শেষ হওয়ার আগেই রেফারেন্সটি আর ব্যবহৃত হচ্ছে না।

যদিও ধার করার এররগুলো কখনও কখনও হতাশাজনক হতে পারে, মনে রাখবেন যে এটি রাস্ট কম্পাইলার যা একটি সম্ভাব্য বাগ প্রথম দিকেই (রানটাইমের পরিবর্তে কম্পাইল টাইমে) নির্দেশ করছে এবং আপনাকে ঠিক কোথায় সমস্যাটি তা দেখাচ্ছে। তাহলে আপনাকে আর খুঁজে বের করতে হবে না কেন আপনার ডেটা আপনি যা ভেবেছিলেন তা নয়।

ড্যাংলিং রেফারেন্স (Dangling References)

পয়েন্টারসহ ভাষাগুলোতে, ভুলবশত একটি ড্যাংলিং পয়েন্টার (dangling pointer)—এমন একটি পয়েন্টার যা মেমরির এমন একটি অবস্থানকে নির্দেশ করে যা হয়তো অন্য কাউকে দেওয়া হয়েছে—তৈরি করা সহজ, কোনো মেমরি মুক্ত করার সময় সেই মেমরির একটি পয়েন্টার সংরক্ষণ করে। এর বিপরীতে, রাস্টে, কম্পাইলার গ্যারান্টি দেয় যে রেফারেন্সগুলো কখনই ড্যাংলিং রেফারেন্স হবে না: যদি আপনার কাছে কিছু ডেটার একটি রেফারেন্স থাকে, কম্পাইলার নিশ্চিত করবে যে ডেটার রেফারেন্সের আগে ডেটা স্কোপের বাইরে যাবে না।

আসুন একটি ড্যাংলিং রেফারেন্স তৈরি করার চেষ্টা করি যাতে দেখা যায় রাস্ট কীভাবে একটি কম্পাইল-টাইম এরর দিয়ে এগুলো প্রতিরোধ করে:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

এখানে এররটি হলো:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
5 | fn dangle() -> &'static String {
  |                 +++++++
help: instead, you are more likely to want to return an owned value
  |
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
  |

error[E0515]: cannot return reference to local variable `s`
 --> src/main.rs:8:5
  |
8 |     &s
  |     ^^ returns a reference to data owned by the current function

Some errors have detailed explanations: E0106, E0515.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 2 previous errors

এই এরর বার্তাটি এমন একটি বৈশিষ্ট্য উল্লেখ করে যা আমরা এখনও আলোচনা করিনি: লাইফটাইম (lifetimes)। আমরা অধ্যায় ১০-এ লাইফটাইম নিয়ে বিস্তারিত আলোচনা করব। কিন্তু, আপনি যদি লাইফটাইম সম্পর্কিত অংশগুলো উপেক্ষা করেন, বার্তাটিতে এই কোডটি কেন একটি সমস্যা তার মূল চাবিকাঠি রয়েছে:

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

আসুন আমাদের dangle কোডের প্রতিটি পর্যায়ে ঠিক কী ঘটছে তা আরও ঘনিষ্ঠভাবে দেখি:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle returns a reference to a String

    let s = String::from("hello"); // s is a new String

    &s // we return a reference to the String, s
} // Here, s goes out of scope and is dropped, so its memory goes away.
  // Danger!

যেহেতু s dangle এর ভিতরে তৈরি করা হয়েছে, তাই dangle-এর কোড শেষ হয়ে গেলে, s ডিঅ্যালোকেট (deallocated) করা হবে। কিন্তু আমরা এটির একটি রেফারেন্স ফেরত দেওয়ার চেষ্টা করেছি। এর মানে এই রেফারেন্সটি একটি অবৈধ String-কে নির্দেশ করবে। এটা মোটেও ভালো না! রাস্ট আমাদের এটা করতে দেবে না।

এখানের সমাধান হলো String সরাসরি ফেরত দেওয়া:

fn main() {
    let string = no_dangle();
}

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

এটি কোনো সমস্যা ছাড়াই কাজ করে। মালিকানা মুভ (moved out) হয়ে যায়, এবং কিছুই ডিঅ্যালোকেট করা হয় না।

রেফারেন্সের নিয়মাবলী (The Rules of References)

আসুন আমরা রেফারেন্স সম্পর্কে যা আলোচনা করেছি তা সংক্ষেপে দেখে নিই:

  • যেকোনো নির্দিষ্ট সময়ে, আপনার কাছে হয় একটি পরিবর্তনযোগ্য রেফারেন্স অথবা যেকোনো সংখ্যক অপরিবর্তনীয় রেফারেন্স থাকতে পারে।
  • রেফারেন্স অবশ্যই সবসময় বৈধ হতে হবে।

এর পরে, আমরা একটি ভিন্ন ধরনের রেফারেন্স দেখব: স্লাইস (slices)।