ماذا سنتعلم اليوم؟
سنتعمق في العمليات الحسابية الأساسية وكيفية إجراء تحويل آمن وفعال بين أنواع البيانات المختلفة في Rust، مع التركيز على الممارسات الآمنة لتجنب الأخطاء الشائعة.
الخطوة 1: العمليات الحسابية الأساسية في Rust
تُعد العمليات الحسابية جوهر أي تطبيق برمجي. في Rust، يمكننا إجراء الجمع، الطرح، الضرب، القسمة، وباقي القسمة بسهولة. دعونا نرى كيف:
fn main() {
let a: i32 = 10; // تعريف متغير صحيح من نوع i32 بالقيمة 10
let b: i32 = 4; // تعريف متغير صحيح آخر بالقيمة 4
// الجمع
let sum = a + b;
println!("الجمع: {} + {} = {}", a, b, sum); // طباعة نتيجة الجمع
// الطرح
let difference = a - b;
println!("الطرح: {} - {} = {}", a, b, difference); // طباعة نتيجة الطرح
// الضرب
let product = a * b;
println!("الضرب: {} * {} = {}", a, b, product); // طباعة نتيجة الضرب
// القسمة
// القسمة بين الأعداد الصحيحة تنتج عددًا صحيحًا (الجزء الصحيح فقط)
let quotient = a / b;
println!("القسمة الصحيحة: {} / {} = {}", a, b, quotient); // طباعة نتيجة القسمة الصحيحة
// باقي القسمة (Modulo)
let remainder = a % b;
println!("باقي القسمة: {} % {} = {}", a, b, remainder); // طباعة باقي القسمة
}ملاحظة تقنية: في Rust، القسمة بين عددين صحيحين (مثلi32) تنتج دائمًا عددًا صحيحًا، متجاهلة الجزء الكسري. للحصول على قسمة عشرية، يجب أن تكون الأرقام من نوع نقطة عائمة (مثلf32أوf64).
الخطوة 2: تحديات التحويل بين أنواع البيانات
أحد التحديات الشائعة في البرمجة هو التعامل مع أنواع البيانات المختلفة. محاولة إجراء عمليات حسابية مباشرة بين أنواع غير متوافقة قد يؤدي إلى أخطاء في الترجمة أو سلوك غير متوقع في وقت التشغيل. Rust صارمة جدًا في هذا الشأن لضمان الأمان.
على سبيل المثال، لا يمكنك ببساطة جمع i32 مع f64 مباشرة. يجب عليك تحويل أحدهما ليطابق الآخر.
fn main() {
let int_val: i32 = 100; // متغير صحيح 32 بت
let float_val: f64 = 10.5; // متغير عشري 64 بت
// هذا السطر سيؤدي إلى خطأ في الترجمة:
// let result = int_val + float_val; // لا يمكن جمع i32 مع f64 مباشرة
// يجب تحويل أحد المتغيرين ليطابق الآخر
// هنا، نقوم بتحويل int_val إلى f64 قبل الجمع
let result_float = int_val as f64 + float_val;
println!("الجمع بعد التحويل إلى عشري: {}", result_float);
}
ملاحظة تقنية: استخدام as للتحويل بين الأنواع البدائية (primitives) هو الطريقة الأكثر شيوعًا في Rust، لكن يجب استخدامه بحذر لأنه يمكن أن يؤدي إلى فقدان البيانات (data loss) أو تجاوز السعة (overflow/underflow) إذا لم يتم التعامل معه بشكل صحيح.
الخطوة 3: التحويل الآمن بين أنواع البيانات (Safe Type Casting)
لضمان التحويل الآمن وتجنب المشاكل مثل تجاوز السعة أو فقدان البيانات، توفر Rust طرقًا أكثر صرامة للتحويل، خاصة عند التحويل من أنواع أكبر إلى أنواع أصغر أو عند التحويلات التي قد تفشل. إحدى هذه الطرق هي استخدام trait TryInto.
use std::convert::TryInto; // استيراد trait TryInto
fn main() {
let large_num: i64 = 2_000_000_000; // رقم كبير من نوع i64 (2 مليار)
let small_num: i32 = 100; // رقم صغير من نوع i32
// تحويل آمن من i64 إلى i32 باستخدام try_into()
// try_into() تُرجع Result<T, E>، مما يجبرنا على التعامل مع حالة الفشل
let converted_large_num: Result<i32, _> = large_num.try_into();
match converted_large_num {
Ok(val) => println!("تم التحويل بنجاح: i64 إلى i32: {}", val),
Err(e) => println!("فشل التحويل من i64 إلى i32: {}", e),
}
// مثال على تحويل سيفشل (تجاوز سعة i32)
let very_large_num: i64 = 3_000_000_000; // 3 مليار، أكبر من أقصى قيمة لـ i32
let converted_very_large_num: Result<i32, _> = very_large_num.try_into();
match converted_very_large_num {
Ok(val) => println!("تم التحويل بنجاح (رغم التجاوز المتوقع): i64 إلى i32: {}", val),
Err(e) => println!("فشل التحويل من i64 إلى i32 بسبب: {}", e), // هذا المسار سيعمل
}
// مثال على تحويل من f64 إلى i32 باستخدام as (غير آمن إذا كان هناك جزء كسري كبير)
let float_to_int: f64 = 123.789;
let converted_float: i32 = float_to_int as i32; // سيتم اقتطاع الجزء العشري
println!("تحويل f64 إلى i32 باستخدام 'as': {}", converted_float);
let overflow_example: u8 = 250; // u8 يمكن أن يحمل حتى 255
// لتجنب تجاوز السعة، يمكن استخدام طرق مثل checked_add, wrapping_add, saturating_add
println!("مثال على تجاوز السعة في u8 مع الرقم 250:");
let checked_add_result = overflow_example.checked_add(10); // إضافة 10
match checked_add_result {
Some(val) => println!("الجمع الآمن (checked_add): {} + 10 = {}", overflow_example, val),
None => println!("فشل الجمع الآمن (checked_add): {} + 10 تجاوز السعة!", overflow_example),
}
let saturating_add_result = overflow_example.saturating_add(10); // إضافة 10
println!("الجمع المشبع (saturating_add): {} + 10 = {}", overflow_example, saturating_add_result);
let wrapping_add_result = overflow_example.wrapping_add(10); // إضافة 10
println!("الجمع الملتف (wrapping_add): {} + 10 = {}", overflow_example, wrapping_add_result);
}ملاحظة تقنية: عند التحويل من نوع عشري (float) إلى نوع صحيح (integer) باستخدامas، يتم اقتطاع الجزء العشري ببساطة (truncation)، ولا يتم التقريب. هذا يمكن أن يؤدي إلى فقدان البيانات. استخدم.round()قبل التحويل إذا كنت بحاجة إلى التقريب. كما أن العمليات الحسابية على الأعداد الصحيحة في Rust تتعامل مع تجاوز السعة (overflow) بطرق مختلفة حسب وضع الترجمة (debug vs. release). في وضع التصحيح، قد يحدثpanic، بينما في وضع الإصدار، يحدث "التفاف" (wrapping) وهو سلوك غير محدد. استخدم دوال مثلchecked_add()،saturating_add()، أوwrapping_add()للتحكم في هذا السلوك.
الكود النهائي الكامل
إليكم السكربت كاملاً الذي يجمع كل ما تعلمناه اليوم:
use std::convert::TryInto; // استيراد trait TryInto للتحويل الآمن
fn main() {
// --- الخطوة 1: العمليات الحسابية الأساسية ---
let a: i32 = 10; // تعريف متغير صحيح من نوع i32 بالقيمة 10
let b: i32 = 4; // تعريف متغير صحيح آخر بالقيمة 4
println!("--- العمليات الحسابية الأساسية ---");
let sum = a + b;
println!("الجمع: {} + {} = {}", a, b, sum);
let difference = a - b;
println!("الطرح: {} - {} = {}", a, b, difference);
let product = a * b;
println!("الضرب: {} * {} = {}", a, b, product);
let quotient = a / b;
println!("القسمة الصحيحة: {} / {} = {}", a, b, quotient);
let remainder = a % b;
println!("باقي القسمة: {} % {} = {}", a, b, remainder);
println!("\n--- تحديات التحويل بين أنواع البيانات ---");
let int_val: i32 = 100; // متغير صحيح 32 بت
let float_val: f64 = 10.5; // متغير عشري 64 بت
// لا يمكن جمع i32 مع f64 مباشرة، يجب التحويل
let result_float = int_val as f64 + float_val; // تحويل int_val إلى f64
println!("الجمع بعد التحويل إلى عشري: {}", result_float);
println!("\n--- التحويل الآمن بين أنواع البيانات ---");
let large_num: i64 = 2_000_000_000; // رقم كبير من نوع i64 (2 مليار)
let small_num: i32 = 100; // رقم صغير من نوع i32
// تحويل آمن من i64 إلى i32 باستخدام try_into()
let converted_large_num: Result<i32, _> = large_num.try_into();
match converted_large_num {
Ok(val) => println!("تم التحويل بنجاح: i64 ({}) إلى i32: {}", large_num, val),
Err(e) => println!("فشل التحويل من i64 إلى i32: {}", e),
}
// مثال على تحويل سيفشل (تجاوز سعة i32)
let very_large_num: i64 = 3_000_000_000; // 3 مليار، أكبر من أقصى قيمة لـ i32
let converted_very_large_num: Result<i32, _> = very_large_num.try_into();
match converted_very_large_num {
Ok(val) => println!("تم التحويل بنجاح (رغم التجاوز المتوقع): i64 ({}) إلى i32: {}", very_large_num, val),
Err(e) => println!("فشل التحويل من i64 إلى i32 بسبب: {}", e), // هذا المسار سيعمل
}
// مثال على تحويل من f64 إلى i32 باستخدام as (غير آمن إذا كان هناك جزء كسري كبير)
let float_to_int: f64 = 123.789;
let converted_float: i32 = float_to_int as i32; // سيتم اقتطاع الجزء العشري (truncation)
println!("تحويل f64 ({}) إلى i32 باستخدام 'as': {}", float_to_int, converted_float);
let overflow_example: u8 = 250; // u8 يمكن أن يحمل حتى 255
println!("مثال على تجاوز السعة في u8 مع الرقم 250:");
// استخدام checked_add للتعامل الآمن مع تجاوز السعة
let checked_add_result = overflow_example.checked_add(10); // إضافة 10
match checked_add_result {
Some(val) => println!("الجمع الآمن (checked_add): {} + 10 = {}", overflow_example, val),
None => println!("فشل الجمع الآمن (checked_add): {} + 10 تجاوز السعة!", overflow_example),
}
// استخدام saturating_add للجمع الذي يتوقف عند الحد الأقصى
let saturating_add_result = overflow_example.saturating_add(10); // إضافة 10
println!("الجمع المشبع (saturating_add): {} + 10 = {}", overflow_example, saturating_add_result);
// استخدام wrapping_add للجمع الذي يلتف حول الحد الأقصى
let wrapping_add_result = overflow_example.wrapping_add(10); // إضافة 10
println!("الجمع الملتف (wrapping_add): {} + 10 = {}", overflow_example, wrapping_add_result);
}النتيجة المتوقعة
عند تشغيل الكود أعلاه باستخدام rustc main.rs && ./main، ستحصل على مخرجات مشابهة لما يلي في الطرفية (قد تختلف رسالة الخطأ الدقيقة لتجاوز السعة قليلاً حسب إصدار Rust وبيئة التشغيل):
--- العمليات الحسابية الأساسية --- الجمع: 10 + 4 = 14 الطرح: 10 - 4 = 6 الضرب: 10 * 4 = 40 القسمة الصحيحة: 10 / 4 = 2 باقي القسمة: 10 % 4 = 2 --- تحديات التحويل بين أنواع البيانات --- الجمع بعد التحويل إلى عشري: 110.5 --- التحويل الآمن بين أنواع البيانات --- تم التحويل بنجاح: i64 (2000000000) إلى i32: 2000000000 فشل التحويل من i64 إلى i32 بسبب: out of range integral type conversion attempted تحويل f64 (123.789) إلى i32 باستخدام 'as': 123 مثال على تجاوز السعة في u8 مع الرقم 250: فشل الجمع الآمن (checked_add): 250 + 10 تجاوز السعة! الجمع المشبع (saturating_add): 250 + 10 = 255 الجمع الملتف (wrapping_add): 250 + 10 = 4
لاحظ كيف أن try_into() التقطت خطأ تجاوز السعة، وكيف قامت as i32 باقتطاع الجزء العشري، وكيف أن checked_add أبلغت عن تجاوز السعة بينما saturating_add وصلت إلى الحد الأقصى و wrapping_add التفتت. هذه الأدوات ضرورية لكتابة كود Rust آمن وموثوق.