Unsafe Rust
এখন পর্যন্ত আমরা যে কোড নিয়ে আলোচনা করেছি, তার সবকিছুতেই রাস্টের memory safety গ্যারান্টি কম্পাইল টাইমে প্রয়োগ করা হয়েছে। তবে, রাস্টের ভেতরে আরও একটি দ্বিতীয় ল্যাঙ্গুয়েজ লুকিয়ে আছে যা এই memory safety গ্যারান্টিগুলো প্রয়োগ করে না: একে বলা হয় unsafe Rust। এটি সাধারণ রাস্টের মতোই কাজ করে, কিন্তু আমাদের কিছু অতিরিক্ত ক্ষমতা বা সুপারপাওয়ার দেয়।
Unsafe Rust এর অস্তিত্বের কারণ হলো, static analysis স্বভাবতই রক্ষণশীল (conservative) হয়। যখন কম্পাইলার কোনো কোড গ্যারান্টিগুলো মেনে চলে কি না তা নির্ধারণ করার চেষ্টা করে, তখন কিছু অবৈধ প্রোগ্রাম গ্রহণ করার চেয়ে কিছু বৈধ প্রোগ্রাম বাতিল করে দেওয়া শ্রেয়। যদিও কোডটি ঠিক থাকতেও পারে, কিন্তু রাস্ট কম্পাইলারের কাছে যদি আত্মবিশ্বাসী হওয়ার মতো যথেষ্ট তথ্য না থাকে, তবে এটি কোডটি বাতিল করে দেবে। এই সব ক্ষেত্রে, আপনি unsafe কোড ব্যবহার করে কম্পাইলারকে বলতে পারেন, "বিশ্বাস করো, আমি জানি আমি কী করছি।" তবে সাবধান থাকবেন যে আপনি নিজের ঝুঁকিতেই unsafe Rust ব্যবহার করছেন: যদি আপনি unsafe কোড ভুলভাবে ব্যবহার করেন, তাহলে memory unsafety-এর কারণে বিভিন্ন সমস্যা দেখা দিতে পারে, যেমন null pointer dereferencing।
রাস্টের একটি unsafe সত্তা থাকার আরেকটি কারণ হলো, কম্পিউটারের underlying হার্ডওয়্যার স্বাভাবিকভাবেই unsafe। যদি রাস্ট আপনাকে unsafe অপারেশন করতে না দিত, তবে আপনি নির্দিষ্ট কিছু কাজ করতে পারতেন না। রাস্টকে আপনাকে low-level সিস্টেম প্রোগ্রামিং করার সুবিধা দিতে হয়, যেমন সরাসরি অপারেটিং সিস্টেমের সাথে ইন্টারঅ্যাক্ট করা বা এমনকি নিজের অপারেটিং সিস্টেম লেখা। low-level সিস্টেম প্রোগ্রামিং নিয়ে কাজ করা এই ল্যাঙ্গুয়েজের অন্যতম একটি লক্ষ্য। চলুন দেখি unsafe Rust দিয়ে আমরা কী করতে পারি এবং কীভাবে তা করতে পারি।
Unsafe Superpowers
Unsafe Rust-এ সুইচ করতে, unsafe
কীওয়ার্ডটি ব্যবহার করুন এবং তারপর একটি নতুন ব্লক শুরু করুন যেখানে unsafe কোড থাকবে। আপনি unsafe Rust-এ পাঁচটি কাজ করতে পারেন যা safe Rust-এ করা যায় না, যেগুলোকে আমরা unsafe superpowers বলি। সেই সুপারপাওয়ারগুলোর মধ্যে রয়েছে:
- একটি raw pointer dereference করা
- একটি unsafe function বা method কল করা
- একটি mutable static variable অ্যাক্সেস বা মডিফাই করা
- একটি unsafe trait ইমপ্লিমেন্ট করা
union
-এর ফিল্ড অ্যাক্সেস করা
এটা বোঝা গুরুত্বপূর্ণ যে unsafe
borrow checker বন্ধ করে না বা রাস্টের অন্য কোনো সেফটি চেক নিষ্ক্রিয় করে না: আপনি যদি unsafe কোডে একটি reference ব্যবহার করেন, তবে তা 여전히 চেক করা হবে। unsafe
কীওয়ার্ডটি আপনাকে শুধুমাত্র এই পাঁচটি ফিচারের অ্যাক্সেস দেয়, যা কম্পাইলার memory safety-এর জন্য পরীক্ষা করে না। আপনি একটি unsafe ব্লকের ভেতরেও একটি নির্দিষ্ট স্তরের নিরাপত্তা পাবেন।
এছাড়াও, unsafe
মানে এই নয় যে ব্লকের ভেতরের কোডটি বিপজ্জনক বা এতে অবশ্যই memory safety সমস্যা থাকবে: এর উদ্দেশ্য হলো, প্রোগ্রামার হিসেবে আপনি নিশ্চিত করবেন যে unsafe
ব্লকের ভেতরের কোডটি বৈধ উপায়ে মেমরি অ্যাক্সেস করবে।
মানুষ ভুল করে এবং ভুল হবেই, কিন্তু এই পাঁচটি unsafe অপারেশনকে unsafe
দিয়ে চিহ্নিত ব্লকের মধ্যে রাখার ফলে আপনি জানতে পারবেন যে memory safety সম্পর্কিত যেকোনো ত্রুটি অবশ্যই একটি unsafe
ব্লকের মধ্যেই রয়েছে। unsafe
ব্লকগুলো ছোট রাখুন; পরে যখন আপনি মেমরি বাগ তদন্ত করবেন, তখন এর জন্য কৃতজ্ঞ থাকবেন।
Unsafe কোডকে যথাসম্ভব বিচ্ছিন্ন রাখতে, এই ধরনের কোডকে একটি safe abstraction-এর মধ্যে আবদ্ধ করা এবং একটি safe API সরবরাহ করা সবচেয়ে ভালো। এই বিষয়ে আমরা এই চ্যাপ্টারের পরে আলোচনা করব যখন আমরা unsafe function এবং method পরীক্ষা করব। স্ট্যান্ডার্ড লাইব্রেরির কিছু অংশ unsafe কোডের উপর safe abstraction হিসেবে প্রয়োগ করা হয়েছে যা নিরীক্ষিত (audited) হয়েছে। Unsafe কোডকে একটি safe abstraction-এ মোড়ানো হলে unsafe
-এর ব্যবহার সেই সব জায়গায় ছড়িয়ে পড়া থেকে আটকানো যায় যেখানে আপনি বা আপনার ব্যবহারকারীরা unsafe
কোড দিয়ে প্রয়োগ করা ফাংশনালিটি ব্যবহার করতে চাইতে পারেন, কারণ একটি safe abstraction ব্যবহার করা নিরাপদ।
চলুন এক এক করে পাঁচটি unsafe superpower দেখে নেওয়া যাক। আমরা এমন কিছু abstraction-ও দেখব যা unsafe কোডের জন্য একটি safe ইন্টারফেস সরবরাহ করে।
একটি Raw Pointer Dereference করা (Dereferencing a Raw Pointer)
চ্যাপ্টার ৪-এর “Dangling References” সেকশনে আমরা উল্লেখ করেছি যে কম্পাইলার নিশ্চিত করে reference-গুলো সবসময় বৈধ থাকে। Unsafe Rust-এ raw pointers নামে দুটি নতুন টাইপ আছে যা reference-এর মতোই। Reference-এর মতো, raw pointer-ও immutable বা mutable হতে পারে এবং এগুলো যথাক্রমে *const T
এবং *mut T
হিসেবে লেখা হয়। এখানে অ্যাস্টেরিস্ক (*) dereference অপারেটর নয়; এটি টাইপের নামের অংশ। Raw pointer-এর ক্ষেত্রে, immutable মানে হলো পয়েন্টারটি dereference করার পর সরাসরি অ্যাসাইন করা যাবে না।
Reference এবং smart pointer-এর থেকে raw pointer ভিন্ন কারণ:
- এদেরকে borrowing rule উপেক্ষা করার অনুমতি দেওয়া হয়, যেমন একই লোকেশনে immutable এবং mutable উভয় পয়েন্টার অথবা একাধিক mutable পয়েন্টার থাকতে পারে।
- এরা যে বৈধ মেমরিতে পয়েন্ট করবে তার কোনো গ্যারান্টি নেই।
- এদের null হওয়ার অনুমতি আছে।
- এরা কোনো স্বয়ংক্রিয় cleanup প্রয়োগ করে না।
রাস্টের এই গ্যারান্টিগুলো প্রয়োগ করা থেকে বিরত থাকার মাধ্যমে, আপনি গ্যারান্টিযুক্ত নিরাপত্তার বিনিময়ে আরও বেশি পারফরম্যান্স বা অন্য কোনো ল্যাঙ্গুয়েজ বা হার্ডওয়্যারের সাথে ইন্টারফেস করার ক্ষমতা পেতে পারেন, যেখানে রাস্টের গ্যারান্টিগুলো প্রযোজ্য নয়।
লিস্টিং ২০-১ দেখাচ্ছে কীভাবে একটি immutable এবং একটি mutable raw pointer তৈরি করতে হয়।
fn main() { let mut num = 5; let r1 = &raw const num; let r2 = &raw mut num; }
লক্ষ্য করুন যে আমরা এই কোডে unsafe
কীওয়ার্ড অন্তর্ভুক্ত করিনি। আমরা safe কোডে raw pointer তৈরি করতে পারি; কিন্তু একটি unsafe ব্লকের বাইরে raw pointer dereference করতে পারি না, যা আপনি একটু পরেই দেখতে পাবেন।
আমরা raw borrow অপারেটর ব্যবহার করে raw pointer তৈরি করেছি: &raw const num
একটি *const i32
immutable raw pointer তৈরি করে এবং &raw mut num
একটি *mut i32
mutable raw pointer তৈরি করে। যেহেতু আমরা এগুলো সরাসরি একটি লোকাল ভ্যারিয়েবল থেকে তৈরি করেছি, আমরা জানি যে এই নির্দিষ্ট raw pointer-গুলো বৈধ, কিন্তু যেকোনো raw pointer সম্পর্কে আমরা এই ধারণা করতে পারি না।
এটি দেখানোর জন্য, পরবর্তীতে আমরা এমন একটি raw pointer তৈরি করব যার বৈধতা সম্পর্কে আমরা এতটা নিশ্চিত হতে পারব না, এর জন্য as
কীওয়ার্ড ব্যবহার করে একটি মান কাস্ট করব, raw borrow অপারেটর ব্যবহার না করে। লিস্টিং ২০-২ দেখাচ্ছে কীভাবে মেমরির একটি নির্বিচারী (arbitrary) লোকেশনে একটি raw pointer তৈরি করা যায়। নির্বিচারী মেমরি ব্যবহার করার চেষ্টা করাটা undefined: সেই অ্যাড্রেসে ডেটা থাকতেও পারে বা নাও থাকতে পারে, কম্পাইলার কোডটি এমনভাবে অপটিমাইজ করতে পারে যাতে কোনো মেমরি অ্যাক্সেস না হয়, অথবা প্রোগ্রামটি একটি সেগমেন্টেশন ফল্ট (segmentation fault) দিয়ে বন্ধ হয়ে যেতে পারে। সাধারণত, এই ধরনের কোড লেখার কোনো ভালো কারণ নেই, বিশেষ করে যেখানে আপনি raw borrow অপারেটর ব্যবহার করতে পারেন, তবে এটি সম্ভব।
fn main() { let address = 0x012345usize; let r = address as *const i32; }``` </Listing> মনে রাখবেন যে আমরা safe কোডে raw pointer তৈরি করতে পারি, কিন্তু আমরা raw pointer _dereference_ করতে এবং পয়েন্ট করা ডেটা পড়তে পারি না। লিস্টিং ২০-৩ এ, আমরা একটি raw pointer-এর উপর dereference অপারেটর `*` ব্যবহার করি যার জন্য একটি `unsafe` ব্লক প্রয়োজন। <Listing number="20-3" caption="একটি `unsafe` ব্লকের মধ্যে raw pointers dereference করা"> ```rust fn main() { let mut num = 5; let r1 = &raw const num; let r2 = &raw mut num; unsafe { println!("r1 is: {}", *r1); println!("r2 is: {}", *r2); } }
একটি পয়েন্টার তৈরি করা কোনো ক্ষতি করে না; শুধুমাত্র যখন আমরা এর নির্দেশিত মান অ্যাক্সেস করার চেষ্টা করি, তখনই আমরা একটি অবৈধ মানের সম্মুখীন হতে পারি।
আরও লক্ষ্য করুন যে লিস্টিং ২০-১ এবং ২০-৩ এ, আমরা *const i32
এবং *mut i32
raw pointer তৈরি করেছি যা উভয়ই একই মেমরি লোকেশনে নির্দেশ করে, যেখানে num
স্টোর করা আছে। যদি আমরা এর পরিবর্তে num
-এর জন্য একটি immutable এবং একটি mutable reference তৈরি করার চেষ্টা করতাম, কোডটি কম্পাইল হতো না কারণ রাস্টের ownership rule একই সময়ে কোনো immutable reference-এর সাথে একটি mutable reference-কে অনুমতি দেয় না। Raw pointer ব্যবহার করে, আমরা একই লোকেশনে একটি mutable পয়েন্টার এবং একটি immutable পয়েন্টার তৈরি করতে পারি এবং mutable পয়েন্টারের মাধ্যমে ডেটা পরিবর্তন করতে পারি, যা সম্ভাব্যভাবে একটি data race তৈরি করতে পারে। সাবধান!
এই সব বিপদ থাকা সত্ত্বেও, আপনি কেন raw pointer ব্যবহার করবেন? একটি বড় ব্যবহারের ক্ষেত্র হলো সি (C) কোডের সাথে ইন্টারফেসিং করার সময়, যা আপনি পরবর্তী বিভাগে দেখতে পাবেন। আরেকটি ক্ষেত্র হলো যখন এমন safe abstraction তৈরি করা হয় যা borrow checker বুঝতে পারে না। আমরা প্রথমে unsafe function পরিচিত করাব এবং তারপর unsafe কোড ব্যবহার করে এমন একটি safe abstraction-এর উদাহরণ দেখব।
একটি Unsafe Function বা Method কল করা (Calling an Unsafe Function or Method)
দ্বিতীয় যে অপারেশনটি আপনি একটি unsafe ব্লকে করতে পারেন তা হলো unsafe function কল করা। Unsafe function এবং method দেখতে সাধারণ function এবং method-এর মতোই, কিন্তু তাদের সংজ্ঞার বাকি অংশের আগে একটি অতিরিক্ত unsafe
থাকে। এই প্রসঙ্গে unsafe
কীওয়ার্ডটি নির্দেশ করে যে ফাংশনটির এমন কিছু প্রয়োজনীয়তা রয়েছে যা আমাদের এই ফাংশনটি কল করার সময় পূরণ করতে হবে, কারণ রাস্ট গ্যারান্টি দিতে পারে না যে আমরা এই প্রয়োজনীয়তাগুলো পূরণ করেছি। একটি unsafe
ব্লকের মধ্যে একটি unsafe function কল করার মাধ্যমে, আমরা বলছি যে আমরা এই ফাংশনের ডকুমেন্টেশন পড়েছি এবং আমরা ফাংশনের contract বা চুক্তিগুলো রক্ষা করার দায়িত্ব নিচ্ছি।
এখানে dangerous
নামে একটি unsafe function রয়েছে যা তার বডিতে কিছুই করে না:
fn main() { unsafe fn dangerous() {} unsafe { dangerous(); } }
আমাদের অবশ্যই dangerous
ফাংশনটিকে একটি পৃথক unsafe
ব্লকের মধ্যে কল করতে হবে। যদি আমরা unsafe
ব্লক ছাড়া dangerous
কল করার চেষ্টা করি, আমরা একটি এরর পাব:
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe block
--> src/main.rs:4:5
|
4 | dangerous();
| ^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
For more information about this error, try `rustc --explain E0133`.
error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
unsafe
ব্লকের মাধ্যমে, আমরা রাস্টকে জানাচ্ছি যে আমরা ফাংশনের ডকুমেন্টেশন পড়েছি, আমরা এটি সঠিকভাবে কীভাবে ব্যবহার করতে হয় তা বুঝি, এবং আমরা যাচাই করেছি যে আমরা ফাংশনের contract পূরণ করছি।
একটি unsafe
ফাংশনের বডিতে unsafe অপারেশন করার জন্য, আপনাকে এখনও একটি unsafe
ব্লক ব্যবহার করতে হবে, যেমনটা একটি সাধারণ ফাংশনের মধ্যে করা হয়, এবং যদি আপনি ভুলে যান তবে কম্পাইলার আপনাকে সতর্ক করবে। এটি আমাদের unsafe
ব্লকগুলিকে যতটা সম্ভব ছোট রাখতে সাহায্য করে, কারণ unsafe অপারেশনগুলি পুরো ফাংশন বডি জুড়ে প্রয়োজন নাও হতে পারে।
Unsafe কোডের উপর একটি Safe Abstraction তৈরি করা
শুধু একটি ফাংশনে unsafe কোড থাকলেই পুরো ফাংশনটিকে unsafe হিসেবে চিহ্নিত করার প্রয়োজন নেই। আসলে, unsafe কোডকে একটি safe ফাংশনে মোড়ানো একটি সাধারণ abstraction। উদাহরণস্বরূপ, চলুন স্ট্যান্ডার্ড লাইব্রেরির split_at_mut
ফাংশনটি দেখি, যার জন্য কিছু unsafe কোড প্রয়োজন। আমরা দেখব এটি কীভাবে প্রয়োগ করা যেতে পারে। এই safe মেথডটি mutable slice-এর উপর সংজ্ঞায়িত করা হয়েছে: এটি একটি slice নেয় এবং আর্গুমেন্ট হিসেবে দেওয়া ইনডেক্সে slice-টিকে ভাগ করে দুটি slice তৈরি করে। লিস্টিং ২০-৪ দেখাচ্ছে কীভাবে split_at_mut
ব্যবহার করতে হয়।
fn main() { let mut v = vec![1, 2, 3, 4, 5, 6]; let r = &mut v[..]; let (a, b) = r.split_at_mut(3); assert_eq!(a, &mut [1, 2, 3]); assert_eq!(b, &mut [4, 5, 6]); }
আমরা শুধুমাত্র safe Rust ব্যবহার করে এই ফাংশনটি প্রয়োগ করতে পারি না। একটি প্রচেষ্টা লিস্টিং ২০-৫ এর মতো হতে পারে, যা কম্পাইল হবে না। সরলতার জন্য, আমরা split_at_mut
কে একটি মেথডের পরিবর্তে একটি ফাংশন হিসেবে প্রয়োগ করব এবং শুধুমাত্র i32
মানের slice-এর জন্য, জেনেরিক টাইপ T
-এর জন্য নয়।
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
assert!(mid <= len);
(&mut values[..mid], &mut values[mid..])
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
}
এই ফাংশনটি প্রথমে slice-টির মোট দৈর্ঘ্য (length) নেয়। তারপর এটি নিশ্চিত করে যে প্যারামিটার হিসেবে দেওয়া ইনডেক্সটি slice-এর মধ্যে আছে কি না, এটি দৈর্ঘ্যের চেয়ে কম বা সমান কি না তা পরীক্ষা করে। এই assertion-এর অর্থ হলো যদি আমরা slice ভাগ করার জন্য দৈর্ঘ্যের চেয়ে বড় একটি ইনডেক্স পাস করি, তাহলে ফাংশনটি সেই ইনডেক্স ব্যবহার করার চেষ্টা করার আগেই প্যানিক (panic) করবে।
তারপর আমরা একটি টাপলে (tuple) দুটি mutable slice রিটার্ন করি: একটি আসল slice-এর শুরু থেকে mid
ইনডেক্স পর্যন্ত এবং অন্যটি mid
থেকে slice-এর শেষ পর্যন্ত।
যখন আমরা লিস্টিং ২০-৫ এর কোড কম্পাইল করার চেষ্টা করব, আমরা একটি এরর পাব:
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0499]: cannot borrow `*values` as mutable more than once at a time
--> src/main.rs:6:31
|
1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
| - let's call the lifetime of this reference `'1`
...
6 | (&mut values[..mid], &mut values[mid..])
| --------------------------^^^^^^--------
| | | |
| | | second mutable borrow occurs here
| | first mutable borrow occurs here
| returning this value requires that `*values` is borrowed for `'1`
|
= help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
For more information about this error, try `rustc --explain E0499`.
error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
রাস্টের borrow checker বুঝতে পারে না যে আমরা slice-এর বিভিন্ন অংশ borrow করছি; এটি কেবল জানে যে আমরা একই slice থেকে দুবার borrow করছি। একটি slice-এর বিভিন্ন অংশ borrow করা মৌলিকভাবে ঠিক আছে কারণ দুটি slice ওভারল্যাপিং নয়, কিন্তু রাস্ট এটি জানার মতো যথেষ্ট স্মার্ট নয়। যখন আমরা জানি কোডটি ঠিক আছে, কিন্তু রাস্ট তা জানে না, তখন unsafe কোড ব্যবহার করার সময় আসে।
লিস্টিং ২০-৬ দেখাচ্ছে কীভাবে একটি unsafe
ব্লক, একটি raw pointer, এবং unsafe ফাংশনের কিছু কল ব্যবহার করে split_at_mut
এর প্রয়োগ কাজ করানো যায়।
use std::slice; fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = values.len(); let ptr = values.as_mut_ptr(); assert!(mid <= len); unsafe { ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } } fn main() { let mut vector = vec![1, 2, 3, 4, 5, 6]; let (left, right) = split_at_mut(&mut vector, 3); }
চ্যাপ্টার ৪-এর “The Slice Type” সেকশন থেকে মনে করুন যে একটি slice হলো কিছু ডেটার একটি পয়েন্টার এবং slice-টির দৈর্ঘ্য। আমরা একটি slice-এর দৈর্ঘ্য পেতে len
মেথড এবং একটি slice-এর raw pointer অ্যাক্সেস করতে as_mut_ptr
মেথড ব্যবহার করি। এই ক্ষেত্রে, যেহেতু আমাদের কাছে i32
মানের একটি mutable slice আছে, as_mut_ptr
একটি *mut i32
টাইপের raw pointer রিটার্ন করে, যা আমরা ptr
ভ্যারিয়েবলে সংরক্ষণ করেছি।
আমরা mid
ইনডেক্সটি slice-এর মধ্যে থাকার assertion টি বজায় রাখি। তারপর আমরা unsafe কোডে আসি: slice::from_raw_parts_mut
ফাংশনটি একটি raw pointer এবং একটি দৈর্ঘ্য নেয়, এবং এটি একটি slice তৈরি করে। আমরা এই ফাংশনটি ব্যবহার করে একটি slice তৈরি করি যা ptr
থেকে শুরু হয় এবং mid
আইটেম দীর্ঘ। তারপর আমরা ptr
-এর উপর add
মেথড কল করি mid
কে আর্গুমেন্ট হিসেবে দিয়ে একটি raw pointer পেতে যা mid
থেকে শুরু হয়, এবং আমরা সেই পয়েন্টার এবং mid
-এর পরে থাকা বাকি আইটেমের সংখ্যাকে দৈর্ঘ্য হিসেবে ব্যবহার করে একটি slice তৈরি করি।
slice::from_raw_parts_mut
ফাংশনটি unsafe কারণ এটি একটি raw pointer নেয় এবং বিশ্বাস করতে হয় যে এই পয়েন্টারটি বৈধ। Raw pointer-এর উপর add
মেথডটিও unsafe কারণ এটিকে বিশ্বাস করতে হয় যে অফসেট লোকেশনটিও একটি বৈধ পয়েন্টার। তাই, আমাদের slice::from_raw_parts_mut
এবং add
-এ আমাদের কলগুলোর চারপাশে একটি unsafe
ব্লক রাখতে হয়েছিল যাতে আমরা সেগুলি কল করতে পারি। কোডটি দেখে এবং mid
অবশ্যই len
-এর চেয়ে কম বা সমান হতে হবে এই assertion যোগ করে, আমরা বলতে পারি যে unsafe
ব্লকের মধ্যে ব্যবহৃত সমস্ত raw pointer slice-এর মধ্যে থাকা ডেটার বৈধ পয়েন্টার হবে। এটি unsafe
-এর একটি গ্রহণযোগ্য এবং উপযুক্ত ব্যবহার।
লক্ষ্য করুন যে আমাদের ফলস্বরূপ split_at_mut
ফাংশনটিকে unsafe
হিসেবে চিহ্নিত করার প্রয়োজন নেই, এবং আমরা এই ফাংশনটিকে safe Rust থেকে কল করতে পারি। আমরা unsafe কোডের জন্য একটি safe abstraction তৈরি করেছি এমন একটি ফাংশনের প্রয়োগের সাথে যা unsafe
কোডকে নিরাপদ উপায়ে ব্যবহার করে, কারণ এটি কেবল সেই ডেটা থেকে বৈধ পয়েন্টার তৈরি করে যা এই ফাংশনটির অ্যাক্সেস আছে।
বিপরীতে, লিস্টিং ২০-৭-এ slice::from_raw_parts_mut
-এর ব্যবহার সম্ভবত slice ব্যবহার করার সময় ক্র্যাশ করবে। এই কোডটি একটি নির্বিচারী মেমরি লোকেশন নেয় এবং ১০,০০০ আইটেম দীর্ঘ একটি slice তৈরি করে।
fn main() { use std::slice; let address = 0x01234usize; let r = address as *mut i32; let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) }; }
আমরা এই নির্বিচারী লোকেশনের মেমরির মালিক নই, এবং এমন কোনো গ্যারান্টি নেই যে এই কোডটি যে slice তৈরি করে তাতে বৈধ i32
মান রয়েছে। values
-কে একটি বৈধ slice হিসেবে ব্যবহার করার চেষ্টা করলে undefined behavior ঘটে।
এক্সটার্নাল কোড কল করার জন্য extern
ফাংশন ব্যবহার করা
কখনও কখনও আপনার রাস্ট কোডকে অন্য ভাষায় লেখা কোডের সাথে ইন্টারঅ্যাক্ট করার প্রয়োজন হতে পারে। এর জন্য, রাস্টের extern
কীওয়ার্ড রয়েছে যা একটি Foreign Function Interface (FFI) তৈরি এবং ব্যবহারে সহায়তা করে। FFI হল একটি প্রোগ্রামিং ভাষার জন্য ফাংশন সংজ্ঞায়িত করার একটি উপায়, যা একটি ভিন্ন (বিদেশী) প্রোগ্রামিং ভাষাকে সেই ফাংশনগুলিকে কল করতে সক্ষম করে।
লিস্টিং ২০-৮ দেখাচ্ছে কীভাবে সি স্ট্যান্ডার্ড লাইব্রেরি থেকে abs
ফাংশনের সাথে ইন্টিগ্রেশন সেট আপ করতে হয়। extern
ব্লকের মধ্যে ঘোষিত ফাংশনগুলি রাস্ট কোড থেকে কল করা সাধারণত unsafe হয়, তাই extern
ব্লকগুলিকেও unsafe
হিসাবে চিহ্নিত করতে হবে। এর কারণ হল অন্যান্য ভাষা রাস্টের নিয়ম এবং গ্যারান্টি প্রয়োগ করে না, এবং রাস্ট সেগুলি পরীক্ষা করতে পারে না, তাই নিরাপত্তা নিশ্চিত করার দায়িত্ব প্রোগ্রামারের উপর বর্তায়।
unsafe extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3 according to C: {}", abs(-3)); } }
unsafe extern "C"
ব্লকের মধ্যে, আমরা অন্য ভাষা থেকে যে এক্সটার্নাল ফাংশনগুলি কল করতে চাই তাদের নাম এবং সিগনেচার তালিকাভুক্ত করি। "C"
অংশটি সংজ্ঞায়িত করে যে এক্সটার্নাল ফাংশনটি কোন application binary interface (ABI) ব্যবহার করে: ABI অ্যাসেম্বলি লেভেলে ফাংশনটি কীভাবে কল করতে হয় তা নির্ধারণ করে। "C"
ABI সবচেয়ে সাধারণ এবং সি প্রোগ্রামিং ভাষার ABI অনুসরণ করে। রাস্টের সমর্থিত সমস্ত ABI সম্পর্কে তথ্য the Rust Reference-এ পাওয়া যায়।
একটি unsafe extern
ব্লকের মধ্যে ঘোষিত প্রতিটি আইটেম অন্তর্নিহিতভাবে unsafe। তবে, কিছু FFI ফাংশন কল করা safe। উদাহরণস্বরূপ, সি-এর স্ট্যান্ডার্ড লাইব্রেরির abs
ফাংশনে কোনো memory safety সংক্রান্ত বিবেচনা নেই এবং আমরা জানি এটি যেকোনো i32
দিয়ে কল করা যেতে পারে। এই ধরনের ক্ষেত্রে, আমরা safe
কীওয়ার্ড ব্যবহার করে বলতে পারি যে এই নির্দিষ্ট ফাংশনটি কল করা safe যদিও এটি একটি unsafe extern
ব্লকের মধ্যে রয়েছে। একবার আমরা সেই পরিবর্তনটি করলে, এটি কল করার জন্য আর একটি unsafe
ব্লকের প্রয়োজন হয় না, যেমনটি লিস্টিং ২০-৯-এ দেখানো হয়েছে।
unsafe extern "C" { safe fn abs(input: i32) -> i32; } fn main() { println!("Absolute value of -3 according to C: {}", abs(-3)); }
একটি ফাংশনকে safe
হিসাবে চিহ্নিত করা এটিকে অন্তর্নিহিতভাবে safe করে তোলে না! পরিবর্তে, এটি রাস্টের কাছে আপনার করা একটি প্রতিশ্রুতির মতো যে এটি safe। সেই প্রতিশ্রুতি রক্ষা করা হয়েছে কিনা তা নিশ্চিত করার দায়িত্ব এখনও আপনার!
অন্য ভাষা থেকে রাস্ট ফাংশন কল করা
আমরা extern
ব্যবহার করে একটি ইন্টারফেস তৈরি করতে পারি যা অন্য ভাষাকে রাস্ট ফাংশন কল করার অনুমতি দেয়। একটি সম্পূর্ণ extern
ব্লক তৈরি করার পরিবর্তে, আমরা extern
কীওয়ার্ড যোগ করি এবং সংশ্লিষ্ট ফাংশনের fn
কীওয়ার্ডের ঠিক আগে ব্যবহার করার জন্য ABI নির্দিষ্ট করি। আমাদের এই ফাংশনের নামটি যাতে রাস্ট কম্পাইলার ম্যাঙ্গল (mangle) না করে তা বলার জন্য একটি #[unsafe(no_mangle)]
টীকাও যোগ করতে হবে। Mangling হল যখন একটি কম্পাইলার আমাদের দেওয়া একটি ফাংশনের নামকে একটি ভিন্ন নামে পরিবর্তন করে যা কম্পাইলেশন প্রক্রিয়ার অন্যান্য অংশগুলির জন্য আরও তথ্য ধারণ করে কিন্তু মানুষের জন্য কম পাঠযোগ্য হয়। প্রতিটি প্রোগ্রামিং ভাষার কম্পাইলার নামগুলিকে সামান্য ভিন্নভাবে ম্যাঙ্গল করে, তাই একটি রাস্ট ফাংশনকে অন্য ভাষা দ্বারা নামকরণযোগ্য করার জন্য, আমাদের অবশ্যই রাস্ট কম্পাইলারের নাম ম্যাংলিং নিষ্ক্রিয় করতে হবে। এটি unsafe কারণ বিল্ট-ইন ম্যাংলিং ছাড়া লাইব্রেরি জুড়ে নামের সংঘর্ষ হতে পারে, তাই আমরা যে নামটি বেছে নিই তা ম্যাংলিং ছাড়াই এক্সপোর্ট করার জন্য safe কিনা তা নিশ্চিত করা আমাদের দায়িত্ব।
নিম্নলিখিত উদাহরণে, আমরা call_from_c
ফাংশনটিকে সি কোড থেকে অ্যাক্সেসযোগ্য করে তুলি, এটি একটি শেয়ার্ড লাইব্রেরিতে কম্পাইল হওয়ার এবং সি থেকে লিঙ্ক করার পরে:
#[unsafe(no_mangle)]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
extern
-এর এই ব্যবহারে শুধুমাত্র অ্যাট্রিবিউটে unsafe
প্রয়োজন, extern
ব্লকে নয়।
একটি Mutable Static Variable অ্যাক্সেস বা মডিফাই করা
এই বইয়ে আমরা এখনও গ্লোবাল ভ্যারিয়েবল নিয়ে কথা বলিনি, যা রাস্ট সমর্থন করে কিন্তু রাস্টের ownership rules-এর সাথে সমস্যাযুক্ত হতে পারে। যদি দুটি থ্রেড একই mutable গ্লোবাল ভ্যারিয়েবল অ্যাক্সেস করে, তবে এটি একটি data race ঘটাতে পারে।
রাস্টে, গ্লোবাল ভ্যারিয়েবলকে static ভ্যারিয়েবল বলা হয়। লিস্টিং ২০-১০ একটি স্ট্রিং স্লাইস মান সহ একটি স্ট্যাটিক ভেরিয়েবলের ঘোষণা এবং ব্যবহারের উদাহরণ দেখায়।
static HELLO_WORLD: &str = "Hello, world!"; fn main() { println!("value is: {HELLO_WORLD}"); }
Static ভ্যারিয়েবলগুলো ধ্রুবকের (constants) মতো, যা আমরা চ্যাপ্টার ৩-এর “Constants” সেকশনে আলোচনা করেছি। Static ভ্যারিয়েবলের নাম প্রথা অনুযায়ী SCREAMING_SNAKE_CASE
-এ থাকে। Static ভ্যারিয়েবলগুলো কেবল 'static
লাইফটাইম সহ reference সংরক্ষণ করতে পারে, যার মানে হল রাস্ট কম্পাইলার লাইফটাইম বের করতে পারে এবং আমাদের স্পষ্টভাবে এটি চিহ্নিত করার প্রয়োজন নেই। একটি immutable static variable অ্যাক্সেস করা নিরাপদ।
ধ্রুবক এবং immutable static ভ্যারিয়েবলের মধ্যে একটি সূক্ষ্ম পার্থক্য হল যে একটি static ভ্যারিয়েবলের মান মেমরিতে একটি নির্দিষ্ট ঠিকানা থাকে। মানটি ব্যবহার করলে সর্বদা একই ডেটা অ্যাক্সেস করা হবে। অন্যদিকে, ধ্রুবকগুলি যখনই ব্যবহার করা হয় তখন তাদের ডেটা নকল করার অনুমতি দেওয়া হয়। আরেকটি পার্থক্য হল static ভ্যারিয়েবলগুলো mutable হতে পারে। Mutable static ভ্যারিয়েবল অ্যাক্সেস এবং মডিফাই করা unsafe। লিস্টিং ২০-১১ দেখায় কীভাবে COUNTER
নামের একটি mutable static ভ্যারিয়েবল ঘোষণা, অ্যাক্সেস এবং মডিফাই করতে হয়।
static mut COUNTER: u32 = 0; /// SAFETY: Calling this from more than a single thread at a time is undefined /// behavior, so you *must* guarantee you only call it from a single thread at /// a time. unsafe fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { unsafe { // SAFETY: This is only called from a single thread in `main`. add_to_count(3); println!("COUNTER: {}", *(&raw const COUNTER)); } }
সাধারণ ভ্যারিয়েবলের মতো, আমরা mut
কীওয়ার্ড ব্যবহার করে মিউটেবিলিটি নির্দিষ্ট করি। COUNTER
থেকে পড়া বা লেখার যেকোনো কোড অবশ্যই একটি unsafe
ব্লকের মধ্যে থাকতে হবে। লিস্টিং ২০-১১-এর কোডটি কম্পাইল হয় এবং COUNTER: 3
প্রিন্ট করে যেমনটি আমরা আশা করি কারণ এটি একক-থ্রেডেড (single-threaded)। একাধিক থ্রেড COUNTER
অ্যাক্সেস করলে সম্ভবত data race হবে, তাই এটি undefined behavior। অতএব, আমাদের পুরো ফাংশনটিকে unsafe
হিসাবে চিহ্নিত করতে হবে এবং সেফটি সীমাবদ্ধতা নথিভুক্ত করতে হবে, যাতে যে কেউ ফাংশনটি কল করে তারা জানে যে তারা নিরাপদে কী করতে পারে এবং কী করতে পারে না।
যখনই আমরা একটি unsafe ফাংশন লিখি, তখন SAFETY
দিয়ে শুরু হওয়া একটি মন্তব্য লেখা এবং কলারকে ফাংশনটি নিরাপদে কল করার জন্য কী করতে হবে তা ব্যাখ্যা করা একটি প্রথা। একইভাবে, যখনই আমরা একটি unsafe অপারেশন সম্পাদন করি, তখন SAFETY
দিয়ে শুরু হওয়া একটি মন্তব্য লেখা এবং সেফটি নিয়মগুলি কীভাবে বজায় রাখা হয় তা ব্যাখ্যা করা একটি প্রথা।
অতিরিক্তভাবে, কম্পাইলার একটি কম্পাইলার লিন্টের মাধ্যমে একটি mutable static ভ্যারিয়েবলের রেফারেন্স তৈরির যেকোনো প্রচেষ্টা ডিফল্টরূপে অস্বীকার করবে। আপনাকে অবশ্যই #[allow(static_mut_refs)]
টীকা যোগ করে সেই লিন্টের সুরক্ষা থেকে স্পষ্টভাবে অপ্ট-আউট করতে হবে অথবা raw borrow অপারেটরগুলির একটি দিয়ে তৈরি একটি raw pointer-এর মাধ্যমে mutable static ভ্যারিয়েবলটি অ্যাক্সেস করতে হবে। এর মধ্যে এমন ক্ষেত্রগুলিও অন্তর্ভুক্ত রয়েছে যেখানে রেফারেন্সটি অদৃশ্যভাবে তৈরি হয়, যেমন এই কোড তালিকায় println!
-এ ব্যবহৃত হলে। static mutable ভ্যারিয়েবলের রেফারেন্সগুলিকে raw pointer-এর মাধ্যমে তৈরি করার প্রয়োজনীয়তা তাদের ব্যবহারের জন্য সেফটি প্রয়োজনীয়তাগুলিকে আরও স্পষ্ট করতে সহায়তা করে।
বিশ্বব্যাপী অ্যাক্সেসযোগ্য mutable ডেটার সাথে, কোনো data race নেই তা নিশ্চিত করা কঠিন, যে কারণে রাস্ট mutable static ভ্যারিয়েবলগুলিকে unsafe বলে মনে করে। যেখানে সম্ভব, চ্যাপ্টার ১৬-এ আলোচনা করা concurrency কৌশল এবং থ্রেড-সেফ স্মার্ট পয়েন্টারগুলি ব্যবহার করা বাঞ্ছনীয় যাতে কম্পাইলার পরীক্ষা করে যে বিভিন্ন থ্রেড থেকে ডেটা অ্যাক্সেস নিরাপদে করা হয়েছে।
একটি Unsafe Trait ইমপ্লিমেন্ট করা (Implementing an Unsafe Trait)
আমরা একটি unsafe trait ইমপ্লিমেন্ট করতে unsafe
ব্যবহার করতে পারি। একটি trait unsafe হয় যখন এর অন্তত একটি মেথডের এমন কোনো invariant থাকে যা কম্পাইলার যাচাই করতে পারে না। আমরা trait
-এর আগে unsafe
কীওয়ার্ড যোগ করে একটি trait-কে unsafe
ঘোষণা করি এবং trait-এর ইমপ্লিমেন্টেশনটিকেও unsafe
হিসেবে চিহ্নিত করি, যেমনটি লিস্টিং ২০-১২-এ দেখানো হয়েছে।
unsafe trait Foo { // methods go here } unsafe impl Foo for i32 { // method implementations go here } fn main() {}
unsafe impl
ব্যবহার করে, আমরা প্রতিশ্রুতি দিচ্ছি যে আমরা সেইসব invariant বজায় রাখব যা কম্পাইলার যাচাই করতে পারে না।
উদাহরণস্বরূপ, চ্যাপ্টার ১৬-এর “Extensible Concurrency with the Send
and Sync
Traits” সেকশনে আলোচনা করা Send
এবং Sync
মার্কার trait-গুলোর কথা মনে করুন: যদি আমাদের টাইপগুলো সম্পূর্ণরূপে Send
এবং Sync
ইমপ্লিমেন্ট করা অন্যান্য টাইপ দ্বারা গঠিত হয় তবে কম্পাইলার এই trait-গুলো স্বয়ংক্রিয়ভাবে ইমপ্লিমেন্ট করে। যদি আমরা এমন একটি টাইপ ইমপ্লিমেন্ট করি যাতে Send
বা Sync
ইমপ্লিমেন্ট করে না এমন একটি টাইপ থাকে, যেমন raw pointers, এবং আমরা সেই টাইপটিকে Send
বা Sync
হিসেবে চিহ্নিত করতে চাই, তবে আমাদের অবশ্যই unsafe
ব্যবহার করতে হবে। রাস্ট যাচাই করতে পারে না যে আমাদের টাইপটি থ্রেড জুড়ে নিরাপদে পাঠানো বা একাধিক থ্রেড থেকে অ্যাক্সেস করার গ্যারান্টি বজায় রাখে; তাই, আমাদের সেইসব পরীক্ষা ম্যানুয়ালি করতে হবে এবং unsafe
দিয়ে তা নির্দেশ করতে হবে।
একটি Union-এর ফিল্ড অ্যাক্সেস করা (Accessing Fields of a Union)
শেষ যে কাজটি শুধুমাত্র unsafe
দিয়ে করা যায় তা হল একটি union-এর ফিল্ড অ্যাক্সেস করা। একটি union struct
-এর মতোই, কিন্তু একটি নির্দিষ্ট ইনস্ট্যান্সে একবারে শুধুমাত্র একটি ঘোষিত ফিল্ড ব্যবহার করা হয়। Union প্রধানত সি কোডে union-এর সাথে ইন্টারফেস করার জন্য ব্যবহৃত হয়। Union ফিল্ড অ্যাক্সেস করা unsafe কারণ রাস্ট গ্যারান্টি দিতে পারে না যে union ইনস্ট্যান্সে বর্তমানে কোন ধরনের ডেটা সংরক্ষণ করা হচ্ছে। আপনি the Rust Reference-এ union সম্পর্কে আরও জানতে পারেন।
Unsafe কোড পরীক্ষা করার জন্য Miri ব্যবহার করা (Using Miri to Check Unsafe Code)
Unsafe কোড লেখার সময়, আপনি যা লিখেছেন তা আসলে নিরাপদ এবং সঠিক কিনা তা পরীক্ষা করতে চাইতে পারেন। এটি করার অন্যতম সেরা উপায় হল Miri ব্যবহার করা, যা undefined behavior সনাক্ত করার জন্য একটি অফিসিয়াল রাস্ট টুল। যেখানে borrow checker একটি static টুল যা কম্পাইল টাইমে কাজ করে, Miri একটি dynamic টুল যা রানটাইমে কাজ করে। এটি আপনার প্রোগ্রাম বা এর টেস্ট স্যুট চালিয়ে আপনার কোড পরীক্ষা করে এবং যখন আপনি রাস্টের নিয়ম লঙ্ঘন করেন তখন তা সনাক্ত করে।
Miri ব্যবহার করার জন্য রাস্টের একটি নাইটলি বিল্ড প্রয়োজন (যা আমরা Appendix G: How Rust is Made and “Nightly Rust”-এ আরও আলোচনা করি)। আপনি rustup +nightly component add miri
টাইপ করে রাস্টের একটি নাইটলি সংস্করণ এবং Miri টুল উভয়ই ইনস্টল করতে পারেন। এটি আপনার প্রোজেক্ট কোন রাস্ট সংস্করণ ব্যবহার করে তা পরিবর্তন করে না; এটি কেবল আপনার সিস্টেমে টুলটি যুক্ত করে যাতে আপনি যখন চান তখন এটি ব্যবহার করতে পারেন। আপনি একটি প্রোজেক্টে cargo +nightly miri run
বা cargo +nightly miri test
টাইপ করে Miri চালাতে পারেন।
এটি কতটা সহায়ক হতে পারে তার একটি উদাহরণের জন্য, লিস্টিং ২০-৭ এর বিরুদ্ধে এটি চালালে কী ঘটে তা বিবেচনা করুন।
$ cargo +nightly miri run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `file:///home/.rustup/toolchains/nightly/bin/cargo-miri runner target/miri/debug/unsafe-example`
warning: integer-to-pointer cast
--> src/main.rs:5:13
|
5 | let r = address as *mut i32;
| ^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast
|
= help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program
= help: see https://doc.rust-lang.org/nightly/std/ptr/fn.with_exposed_provenance.html for more details on that operation
= help: to ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead
= help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics
= help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning
= note: BACKTRACE:
= note: inside `main` at src/main.rs:5:13: 5:32
error: Undefined Behavior: pointer not dereferenceable: pointer must be dereferenceable for 40000 bytes, but got 0x1234[noalloc] which is a dangling pointer (it has no provenance)
--> src/main.rs:7:35
|
7 | let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at src/main.rs:7:35: 7:70
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error; 1 warning emitted
Miri সঠিকভাবে আমাদের সতর্ক করে যে আমরা একটি ইন্টিজারকে একটি পয়েন্টারে কাস্ট করছি, যা একটি সমস্যা হতে পারে কিন্তু Miri এটি সনাক্ত করতে পারে না কারণ এটি জানে না পয়েন্টারটি কীভাবে উদ্ভূত হয়েছে। তারপর, Miri একটি ত্রুটি প্রদান করে যেখানে লিস্টিং ২০-৭-এর undefined behavior রয়েছে কারণ আমাদের একটি ড্যাংলিং পয়েন্টার আছে। Miri-কে ধন্যবাদ, আমরা এখন জানি যে undefined behavior-এর ঝুঁকি রয়েছে, এবং আমরা কোডটিকে কীভাবে নিরাপদ করা যায় সে সম্পর্কে ভাবতে পারি। কিছু ক্ষেত্রে, Miri এমনকি ত্রুটিগুলি কীভাবে ঠিক করতে হয় সে সম্পর্কে সুপারিশও করতে পারে।
Unsafe কোড লেখার সময় আপনি যা কিছু ভুল করতে পারেন Miri তার সবকিছু ধরে না। Miri একটি ডায়নামিক বিশ্লেষণ টুল, তাই এটি কেবল সেই কোডের সমস্যাগুলি ধরে যা আসলে চালানো হয়। এর মানে হল আপনার লেখা unsafe কোড সম্পর্কে আপনার আত্মবিশ্বাস বাড়ানোর জন্য আপনাকে এটি ভাল টেস্টিং কৌশলের সাথে ব্যবহার করতে হবে। Miri আপনার কোড unsound হওয়ার সমস্ত সম্ভাব্য উপায়ও কভার করে না।
অন্যভাবে বলতে গেলে: যদি Miri একটি সমস্যা ধরে, আপনি জানেন যে একটি বাগ আছে, কিন্তু শুধু Miri একটি বাগ না ধরার মানে এই নয় যে কোনো সমস্যা নেই। তবে এটি অনেক কিছু ধরতে পারে। এই অধ্যায়ের অন্যান্য unsafe কোডের উদাহরণগুলিতে এটি চালানোর চেষ্টা করুন এবং দেখুন এটি কী বলে!
আপনি Miri সম্পর্কে আরও জানতে পারেন its GitHub repository-তে।
কখন Unsafe কোড ব্যবহার করবেন (When to Use Unsafe Code)
আলোচিত পাঁচটি সুপারপাওয়ারের একটি ব্যবহার করার জন্য unsafe
ব্যবহার করা ভুল বা এমনকি নিন্দনীয়ও নয়, তবে unsafe
কোড সঠিক করা আরও কঠিন কারণ কম্পাইলার মেমরি সেফটি বজায় রাখতে সাহায্য করতে পারে না। যখন আপনার unsafe
কোড ব্যবহার করার কোনো কারণ থাকে, আপনি তা করতে পারেন, এবং সুস্পষ্ট unsafe
টীকা থাকা সমস্যা দেখা দিলে তার উৎস খুঁজে বের করা সহজ করে তোলে। যখনই আপনি unsafe কোড লিখবেন, আপনি Miri ব্যবহার করে আপনার লেখা কোড রাস্টের নিয়ম বজায় রাখে কিনা সে সম্পর্কে আরও আত্মবিশ্বাসী হতে পারেন।
Unsafe Rust-এর সাথে কার্যকরভাবে কীভাবে কাজ করতে হয় সে সম্পর্কে আরও গভীর অনুসন্ধানের জন্য, রাস্টের এই বিষয়ে অফিসিয়াল গাইড, Rustonomicon পড়ুন।