রেফারেন্স এবং বোরোয়িং (References and Borrowing)
Listing 4-5-এর টাপল কোডের সমস্যা হল, calculate_length
ফাংশন কল করার পরেও আমরা যাতে String
ব্যবহার করতে পারি, সেজন্য আমাদের এটিকে কলিং ফাংশনে (calling function) ফেরত দিতে হবে, কারণ String
-টি calculate_length
-এর মধ্যে সরানো (move) হয়েছিল। এর পরিবর্তে, আমরা String
মানের একটি রেফারেন্স দিতে পারি। একটি রেফারেন্স হল একটি পয়েন্টারের মতো, এটি একটি ঠিকানা যা অনুসরণ করে আমরা সেই ঠিকানায় সংরক্ষিত ডেটা অ্যাক্সেস করতে পারি; সেই ডেটার মালিক অন্য কোনো ভেরিয়েবল। পয়েন্টারের বিপরীতে, একটি রেফারেন্স গ্যারান্টি দেয় যে এটি তার লাইফটাইম (lifetime)-এর জন্য একটি নির্দিষ্ট টাইপের বৈধ মানকে নির্দেশ করবে।
এখানে একটি 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
নিই। এই অ্যাম্পারস্যান্ডগুলো (ampersands) রেফারেন্স উপস্থাপন করে এবং এগুলো আপনাকে কোনো মান এর ওনারশিপ না নিয়ে সেটি ব্যবহার করতে দেয়। Figure 4-6 এই ধারণাটি চিত্রিত করে।
Figure 4-6: &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
-এর মানকে রেফার করে কিন্তু এটির মালিকানা নেয় না। যেহেতু রেফারেন্সটির মালিকানা নেই, তাই রেফারেন্সটি ব্যবহার করা বন্ধ হয়ে গেলে এটি যে মানটির দিকে নির্দেশ করে সেটি ড্রপ হবে না।
একইভাবে, ফাংশনের সিগনেচার &
ব্যবহার করে বোঝায় যে প্যারামিটার 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 value is not dropped.
ভেরিয়েবল s
যে স্কোপে বৈধ সেটি যেকোনো ফাংশন প্যারামিটারের স্কোপের মতোই, কিন্তু s
ব্যবহার করা বন্ধ হয়ে গেলে রেফারেন্স দ্বারা নির্দেশিত মানটি ড্রপ হয় না, কারণ s
-এর ওনারশিপ নেই। যখন ফাংশনগুলো প্রকৃত মানের পরিবর্তে প্যারামিটার হিসাবে রেফারেন্স নেয়, তখন আমাদের ওনারশিপ ফিরিয়ে দেওয়ার জন্য মানগুলো রিটার্ন করার প্রয়োজন হবে না, কারণ আমাদের কখনই ওনারশিপ ছিল না।
আমরা একটি রেফারেন্স তৈরির ক্রিয়াকে বোরোয়িং (borrowing) বলি। বাস্তব জীবনের মতো, যদি কোনো ব্যক্তির কাছে কোনো কিছু থাকে, তাহলে আপনি সেটি তার কাছ থেকে ধার নিতে পারেন। আপনার কাজ শেষ হয়ে গেলে, আপনাকে এটি ফিরিয়ে দিতে হবে। আপনি এটির মালিক নন।
তাহলে, আমরা যদি কোনো কিছু ধার করে পরিবর্তন করার চেষ্টা করি তাহলে কী হবে? Listing 4-6-এর কোডটি চেষ্টা করুন। স্পয়লার অ্যালার্ট: এটি কাজ করে না!
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
ভেরিয়েবলগুলো যেমন ডিফল্টরূপে ইমিউটেবল, তেমনই রেফারেন্সগুলোও। আমরা যেটির রেফারেন্স নিয়েছি সেটি পরিবর্তন করার অনুমতি নেই।
মিউটেবল রেফারেন্স (Mutable References)
আমরা Listing 4-6-এর কোডটিকে কয়েকটি ছোট পরিবর্তনের মাধ্যমে ঠিক করতে পারি, যাতে একটি ধার করা মান পরিবর্তন করা যায়। এর জন্য, আমরা একটি মিউটেবল রেফারেন্স ব্যবহার করব:
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);
}
এখানে এররটি দেওয়া হলো:
$ 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
-এর মতো একই ডেটা ধার করে।
একই সময়ে একই ডেটার একাধিক মিউটেবল রেফারেন্স প্রতিরোধ করার সীমাবদ্ধতা মিউটেশনের অনুমতি দেয় কিন্তু খুব নিয়ন্ত্রিত পদ্ধতিতে। এটি এমন কিছু যা নিয়ে নতুন Rustacean-রা সংগ্রাম করে কারণ বেশিরভাগ ল্যাঙ্গুয়েজ আপনাকে যখন খুশি মিউটেট করতে দেয়। এই সীমাবদ্ধতা থাকার সুবিধা হল Rust কম্পাইল করার সময় ডেটা রেস (data races) প্রতিরোধ করতে পারে। একটি ডেটা রেস একটি রেস কন্ডিশনের অনুরূপ এবং এটি ঘটে যখন এই তিনটি আচরণ ঘটে:
- দুই বা ততোধিক পয়েন্টার একই ডেটা অ্যাক্সেস করে।
- অন্তত একটি পয়েন্টার ডেটাতে লেখার জন্য ব্যবহার করা হচ্ছে।
- ডেটাতে অ্যাক্সেস সিঙ্ক্রোনাইজ করার জন্য কোনো মেকানিজম ব্যবহার করা হচ্ছে না।
ডেটা রেস অনির্ধারিত আচরণ ঘটায় এবং রানটাইমে সেগুলোকে ট্র্যাক করার চেষ্টা করার সময় নির্ণয় এবং ঠিক করা কঠিন হতে পারে; Rust ডেটা রেস সহ কোড কম্পাইল করতে অস্বীকার করে এই সমস্যাটি প্রতিরোধ করে!
বরাবরের মতো, আমরা একাধিক মিউটেবল রেফারেন্সের অনুমতি দেওয়ার জন্য একটি নতুন স্কোপ তৈরি করতে কার্লি ব্র্যাকেট ব্যবহার করতে পারি, তবে একযোগে নয়:
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; }
মিউটেবল এবং ইমিউটেবল রেফারেন্স একত্রিত করার জন্য Rust একই ধরনের নিয়ম প্রয়োগ করে। এই কোডটির ফলে একটি এরর হয়:
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!("{}, {}, and {}", r1, r2, 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!("{}, {}, and {}", r1, r2, 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
তৈরি হওয়ার আগে। এই স্কোপগুলো ওভারল্যাপ করে না, তাই এই কোডটি অনুমোদিত: কম্পাইলার বলতে পারে যে রেফারেন্সটি স্কোপের শেষ হওয়ার আগে একটি বিন্দুতে আর ব্যবহার করা হচ্ছে না।
এমনকি যদি বোরোয়িং এররগুলো মাঝে মাঝে হতাশাজনক হতে পারে, মনে রাখবেন যে এটি হল Rust কম্পাইলার একটি সম্ভাব্য বাগ তাড়াতাড়ি (রানটাইমের পরিবর্তে কম্পাইল করার সময়) নির্দেশ করছে এবং আপনাকে ঠিক কোথায় সমস্যাটি রয়েছে তা দেখাচ্ছে। তাহলে আপনাকে ট্র্যাক করতে হবে না কেন আপনার ডেটা আপনার ভাবনার মতো নয়।
ড্যাংলিং রেফারেন্স (Dangling References)
পয়েন্টার সহ ভাষাগুলোতে, ভুলবশত একটি ড্যাংলিং পয়েন্টার (dangling pointer)—একটি পয়েন্টার যা মেমরির এমন একটি লোকেশনকে নির্দেশ করে যা হয়তো অন্য কাউকে দেওয়া হয়েছে—তৈরি করা সহজ, কিছু মেমরি মুক্ত করার সময় সেই মেমরির একটি পয়েন্টার সংরক্ষণ করে। অন্যদিকে, Rust-এ, কম্পাইলার গ্যারান্টি দেয় যে রেফারেন্সগুলো কখনই ড্যাংলিং রেফারেন্স হবে না: যদি আপনার কাছে কিছু ডেটার রেফারেন্স থাকে, তাহলে কম্পাইলার নিশ্চিত করবে যে ডেটার রেফারেন্স শেষ হওয়ার আগে ডেটা স্কোপের বাইরে যাবে না।
আসুন একটি ড্যাংলিং রেফারেন্স তৈরি করার চেষ্টা করি, এটা দেখতে যে Rust কীভাবে কম্পাইল-টাইম এরর দিয়ে সেগুলো প্রতিরোধ করে:
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
এই এরর মেসেজটি এমন একটি ফিচারের কথা বলে যা আমরা এখনও কভার করিনি: লাইফটাইম। আমরা চ্যাপ্টার ১০-এ লাইফটাইম নিয়ে বিস্তারিত আলোচনা করব। কিন্তু, আপনি যদি লাইফটাইম সম্পর্কিত অংশগুলো উপেক্ষা করেন, তাহলে মেসেজটিতে এই কোডটি কেন সমস্যাযুক্ত তার মূল বিষয় রয়েছে:
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
ডিলোক্যাট করা হবে। কিন্তু আমরা এটির একটি রেফারেন্স রিটার্ন করার চেষ্টা করেছি। এর মানে হল এই রেফারেন্সটি একটি অকার্যকর String
-এর দিকে নির্দেশ করবে। সেটি ভালো নয়! Rust আমাদের এটি করতে দেবে না।
এখানে সমাধান হল সরাসরি String
রিটার্ন করা:
fn main() { let string = no_dangle(); } fn no_dangle() -> String { let s = String::from("hello"); s }
এটি কোনো সমস্যা ছাড়াই কাজ করে। ওনারশিপ সরানো হয়েছে এবং কিছুই ডিলোক্যাট করা হয়নি।
রেফারেন্সের নিয়ম (The Rules of References)
আসুন, রেফারেন্স সম্পর্কে আমরা যা আলোচনা করেছি তার পুনরাবৃত্তি করি:
- যেকোনো সময়ে, আপনি হয় একটি মিউটেবল রেফারেন্স অথবা যেকোনো সংখ্যক ইমিউটেবল রেফারেন্স রাখতে পারেন।
- রেফারেন্সগুলো সর্বদাই বৈধ হতে হবে।
এরপর, আমরা একটি ভিন্ন ধরনের রেফারেন্স দেখব: স্লাইস।